forked from Yara724/api
Tidied up the project
This commit is contained in:
8223
package-lock.json
generated
8223
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
80
package.json
80
package.json
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "yara724",
|
"name": "yara724",
|
||||||
"version": "0.0.1",
|
"version": "2.0.0",
|
||||||
"description": "",
|
"description": "",
|
||||||
"author": "",
|
"author": "",
|
||||||
"private": true,
|
"private": true,
|
||||||
@@ -16,55 +16,60 @@
|
|||||||
"test": "jest",
|
"test": "jest",
|
||||||
"test:watch": "jest --watch",
|
"test:watch": "jest --watch",
|
||||||
"test:cov": "jest --coverage",
|
"test:cov": "jest --coverage",
|
||||||
"test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand",
|
"test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/jest/bin/jest --runInBand",
|
||||||
"test:e2e": "jest --config ./test/jest-e2e.json"
|
"test:e2e": "jest --config ./test/jest-e2e.json"
|
||||||
},
|
},
|
||||||
|
"engines": {
|
||||||
|
"npm": ">=10.0.0",
|
||||||
|
"node": ">=20.0.0"
|
||||||
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@fraybabak/kavenegar_nest": "^1.0.5",
|
"@nestjs/axios": "^4.0.1",
|
||||||
"@nestjs/axios": "^3.1.3",
|
"@nestjs/common": "^11.0.17",
|
||||||
"@nestjs/common": "^10.4.15",
|
|
||||||
"@nestjs/config": "^4.0.4",
|
"@nestjs/config": "^4.0.4",
|
||||||
"@nestjs/core": "^10.4.15",
|
"@nestjs/core": "^11.0.1",
|
||||||
"@nestjs/jwt": "^10.2.0",
|
"@nestjs/jwt": "^11.0.2",
|
||||||
"@nestjs/mongoose": "^10.1.0",
|
"@nestjs/mongoose": "^11.0.4",
|
||||||
"@nestjs/platform-express": "^10.4.15",
|
"@nestjs/platform-express": "^11.1.11",
|
||||||
"@nestjs/schedule": "^4.1.2",
|
"@nestjs/serve-static": "^5.0.5",
|
||||||
"@nestjs/serve-static": "^4.0.2",
|
"@nestjs/swagger": "^11.4.4",
|
||||||
"@nestjs/swagger": "^7.4.2",
|
"axios": "^1.16.1",
|
||||||
"axios": "^1.9.0",
|
|
||||||
"class-transformer": "^0.5.1",
|
"class-transformer": "^0.5.1",
|
||||||
"class-validator": "^0.14.1",
|
"class-validator": "^0.15.1",
|
||||||
"dotenv": "^16.4.7",
|
"express": "^5.2.1",
|
||||||
"express": "^4.22.1",
|
|
||||||
"express-basic-auth": "^1.2.1",
|
|
||||||
"fastest-levenshtein": "^1.0.16",
|
"fastest-levenshtein": "^1.0.16",
|
||||||
"form-data": "^4.0.2",
|
|
||||||
"jalali-moment": "^3.3.11",
|
|
||||||
"mongoose": "^8.9.2",
|
"mongoose": "^8.9.2",
|
||||||
"reflect-metadata": "^0.2.2",
|
"reflect-metadata": "^0.2.2",
|
||||||
"rxjs": "^7.8.1",
|
"rxjs": "^7.8.1",
|
||||||
"svg-captcha": "^1.4.0",
|
"svg-captcha": "^1.4.0"
|
||||||
"uuid": "^11.0.3"
|
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@nestjs/cli": "^11.0.14",
|
"@eslint/eslintrc": "^3.2.0",
|
||||||
"@nestjs/schematics": "^10.2.3",
|
"@eslint/js": "^9.18.0",
|
||||||
"@nestjs/testing": "^10.4.15",
|
"@nestjs/cli": "^11.0.0",
|
||||||
|
"@nestjs/schematics": "^11.0.0",
|
||||||
|
"@nestjs/testing": "^11.0.1",
|
||||||
|
"@swc/cli": "^0.8.1",
|
||||||
|
"@swc/core": "^1.10.8",
|
||||||
"@types/express": "^5.0.0",
|
"@types/express": "^5.0.0",
|
||||||
"@types/jest": "^29.5.14",
|
"@types/jest": "^29.5.14",
|
||||||
"@types/multer": "^1.4.12",
|
"@types/multer": "^2.1.0",
|
||||||
"@types/node": "^22.10.2",
|
"@types/node": "^22.10.7",
|
||||||
"@typescript-eslint/eslint-plugin": "^8.18.1",
|
"@types/supertest": "^6.0.2",
|
||||||
"@typescript-eslint/parser": "^8.18.1",
|
"eslint": "^9.18.0",
|
||||||
"eslint": "^9.17.0",
|
"eslint-config-prettier": "^10.0.1",
|
||||||
"eslint-config-prettier": "^9.1.0",
|
"eslint-plugin-prettier": "^5.2.3",
|
||||||
"eslint-plugin-prettier": "^5.2.1",
|
"globals": "^15.14.0",
|
||||||
"jest": "^29.7.0",
|
"jest": "^29.7.0",
|
||||||
"prettier": "^3.4.2",
|
"prettier": "^3.4.2",
|
||||||
|
"source-map-support": "^0.5.21",
|
||||||
|
"supertest": "^7.0.0",
|
||||||
"ts-jest": "^29.2.5",
|
"ts-jest": "^29.2.5",
|
||||||
|
"ts-loader": "^9.5.2",
|
||||||
"ts-node": "^10.9.2",
|
"ts-node": "^10.9.2",
|
||||||
"tsconfig-paths": "^4.2.0",
|
"tsconfig-paths": "^4.2.0",
|
||||||
"typescript": "^5.7.2"
|
"typescript": "^5.7.3",
|
||||||
|
"typescript-eslint": "^8.20.0"
|
||||||
},
|
},
|
||||||
"jest": {
|
"jest": {
|
||||||
"moduleFileExtensions": [
|
"moduleFileExtensions": [
|
||||||
@@ -81,9 +86,12 @@
|
|||||||
"**/*.(t|j)s"
|
"**/*.(t|j)s"
|
||||||
],
|
],
|
||||||
"coverageDirectory": "../coverage",
|
"coverageDirectory": "../coverage",
|
||||||
"testEnvironment": "node",
|
"testEnvironment": "node"
|
||||||
"moduleNameMapper": {
|
},
|
||||||
"^src/(.*)$": "<rootDir>/$1"
|
"pnpm": {
|
||||||
}
|
"onlyBuiltDependencies": [
|
||||||
|
"@nestjs/core",
|
||||||
|
"@swc/core"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,8 +7,7 @@ import {
|
|||||||
OnModuleInit,
|
OnModuleInit,
|
||||||
} from "@nestjs/common";
|
} from "@nestjs/common";
|
||||||
import { ConfigService } from "@nestjs/config";
|
import { ConfigService } from "@nestjs/config";
|
||||||
import axios, { AxiosRequestConfig } from "axios";
|
import { AxiosRequestConfig } from "axios"; // TODO: Change all axios usages to HttpModule
|
||||||
import * as FormData from "form-data";
|
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class AiService implements OnModuleInit {
|
export class AiService implements OnModuleInit {
|
||||||
@@ -54,10 +53,10 @@ export class AiService implements OnModuleInit {
|
|||||||
async onModuleInit() {
|
async onModuleInit() {
|
||||||
try {
|
try {
|
||||||
const res = await this.login();
|
const res = await this.login();
|
||||||
if (res?.accessToken) {
|
// if (res?.accessToken) {
|
||||||
this.accessToken = res.accessToken;
|
// this.accessToken = res.accessToken;
|
||||||
await this.getApiKey();
|
// await this.getApiKey();
|
||||||
}
|
// }
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
// Don't prevent app startup if AI service is temporarily unavailable
|
// Don't prevent app startup if AI service is temporarily unavailable
|
||||||
// The service will attempt to re-authenticate when aiRequestImage is called
|
// The service will attempt to re-authenticate when aiRequestImage is called
|
||||||
@@ -68,14 +67,14 @@ export class AiService implements OnModuleInit {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private async login() {
|
private async login() {
|
||||||
const loginResponse = await axios.request(this.loginOptions);
|
// const loginResponse = await axios.request(this.loginOptions);
|
||||||
return loginResponse.data;
|
// return loginResponse.data;
|
||||||
}
|
}
|
||||||
|
|
||||||
private async getApiKey() {
|
private async getApiKey() {
|
||||||
const profileResponse = await axios.request(this.profileOptions);
|
// const profileResponse = await axios.request(this.profileOptions);
|
||||||
this.apiKey = profileResponse.data.apiKey.key;
|
// this.apiKey = profileResponse.data.apiKey.key;
|
||||||
return this.apiKey;
|
// return this.apiKey;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async aiRequestImage(file: {
|
public async aiRequestImage(file: {
|
||||||
@@ -86,15 +85,15 @@ export class AiService implements OnModuleInit {
|
|||||||
if (!this.accessToken || !this.apiKey) {
|
if (!this.accessToken || !this.apiKey) {
|
||||||
try {
|
try {
|
||||||
const res = await this.login();
|
const res = await this.login();
|
||||||
if (res?.accessToken) {
|
// if (res?.accessToken) {
|
||||||
this.accessToken = res.accessToken;
|
// this.accessToken = res.accessToken;
|
||||||
await this.getApiKey();
|
// await this.getApiKey();
|
||||||
} else {
|
// } else {
|
||||||
throw new HttpException(
|
// throw new HttpException(
|
||||||
"AI Service authentication failed",
|
// "AI Service authentication failed",
|
||||||
HttpStatus.UNAUTHORIZED,
|
// HttpStatus.UNAUTHORIZED,
|
||||||
);
|
// );
|
||||||
}
|
// }
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
throw new HttpException(
|
throw new HttpException(
|
||||||
"AI Service authentication failed",
|
"AI Service authentication failed",
|
||||||
@@ -116,61 +115,61 @@ export class AiService implements OnModuleInit {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const form = new FormData();
|
// const form = new FormData();
|
||||||
const fileStream = createReadStream(filePath);
|
const fileStream = createReadStream(filePath);
|
||||||
|
|
||||||
// Append file with filename if available
|
// Append file with filename if available
|
||||||
if (file.fileName) {
|
if (file.fileName) {
|
||||||
form.append("images", fileStream, file.fileName);
|
// form.append("images", fileStream, file.fileName);
|
||||||
} else {
|
} else {
|
||||||
// Extract filename from path if not provided
|
// Extract filename from path if not provided
|
||||||
const pathParts = filePath.split("/");
|
const pathParts = filePath.split("/");
|
||||||
const extractedFileName = pathParts[pathParts.length - 1];
|
const extractedFileName = pathParts[pathParts.length - 1];
|
||||||
form.append("images", fileStream, extractedFileName);
|
// form.append("images", fileStream, extractedFileName);
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const requestHeaders = {
|
const requestHeaders = {
|
||||||
...this.imageProcessOptions.headers,
|
...this.imageProcessOptions.headers,
|
||||||
...form.getHeaders(),
|
// ...form.getHeaders(),
|
||||||
};
|
};
|
||||||
|
|
||||||
const fs = require("fs");
|
const fs = require("fs");
|
||||||
const stats = fs.statSync(filePath);
|
const stats = fs.statSync(filePath);
|
||||||
|
|
||||||
const response = await axios.request({
|
// const response = await axios.request({
|
||||||
...this.imageProcessOptions,
|
// ...this.imageProcessOptions,
|
||||||
headers: requestHeaders,
|
// headers: requestHeaders,
|
||||||
data: form,
|
// // data: form,
|
||||||
maxContentLength: Infinity,
|
// maxContentLength: Infinity,
|
||||||
maxBodyLength: Infinity,
|
// maxBodyLength: Infinity,
|
||||||
});
|
// });
|
||||||
|
|
||||||
// Validate response structure
|
// Validate response structure
|
||||||
if (!response.data) {
|
// if (!response.data) {
|
||||||
throw new HttpException(
|
// throw new HttpException(
|
||||||
"AI Service returned empty response",
|
// "AI Service returned empty response",
|
||||||
HttpStatus.BAD_GATEWAY,
|
// HttpStatus.BAD_GATEWAY,
|
||||||
);
|
// );
|
||||||
}
|
// }
|
||||||
|
|
||||||
// Check for error in response first (AI service returns 201 with error in body)
|
// // Check for error in response first (AI service returns 201 with error in body)
|
||||||
if (response.data.error) {
|
// if (response.data.error) {
|
||||||
throw new HttpException(
|
// throw new HttpException(
|
||||||
`AI Service error: ${response.data.error}`,
|
// `AI Service error: ${response.data.error}`,
|
||||||
HttpStatus.BAD_GATEWAY,
|
// HttpStatus.BAD_GATEWAY,
|
||||||
);
|
// );
|
||||||
}
|
// }
|
||||||
|
|
||||||
// Check for processed image (downloadLink)
|
// // Check for processed image (downloadLink)
|
||||||
if (!response.data.downloadLink) {
|
// if (!response.data.downloadLink) {
|
||||||
throw new HttpException(
|
// throw new HttpException(
|
||||||
"AI Service did not return processed image (downloadLink missing)",
|
// "AI Service did not return processed image (downloadLink missing)",
|
||||||
HttpStatus.BAD_GATEWAY,
|
// HttpStatus.BAD_GATEWAY,
|
||||||
);
|
// );
|
||||||
}
|
// }
|
||||||
|
|
||||||
return response.data;
|
// return response.data;
|
||||||
} catch (er) {
|
} catch (er) {
|
||||||
// Determine error source
|
// Determine error source
|
||||||
let errorSource = "UNKNOWN";
|
let errorSource = "UNKNOWN";
|
||||||
|
|||||||
@@ -4,9 +4,7 @@ import { Module } from "@nestjs/common";
|
|||||||
import { ConfigModule } from "@nestjs/config";
|
import { ConfigModule } from "@nestjs/config";
|
||||||
import { UnicodeDigitsNormalizeInterceptor } from "./common/interceptors/unicode-digits-normalize.interceptor";
|
import { UnicodeDigitsNormalizeInterceptor } from "./common/interceptors/unicode-digits-normalize.interceptor";
|
||||||
import { MongooseModule } from "@nestjs/mongoose";
|
import { MongooseModule } from "@nestjs/mongoose";
|
||||||
import { ScheduleModule } from "@nestjs/schedule";
|
|
||||||
import { ServeStaticModule } from "@nestjs/serve-static";
|
import { ServeStaticModule } from "@nestjs/serve-static";
|
||||||
import * as dotenv from "dotenv";
|
|
||||||
import { AiModule } from "./ai/ai.module";
|
import { AiModule } from "./ai/ai.module";
|
||||||
import { AuthModule } from "./auth/auth.module";
|
import { AuthModule } from "./auth/auth.module";
|
||||||
import { ClaimRequestManagementModule } from "./claim-request-management/claim-request-management.module";
|
import { ClaimRequestManagementModule } from "./claim-request-management/claim-request-management.module";
|
||||||
@@ -26,13 +24,9 @@ import { applyIranFaTimestampPlugin } from "./helpers/mongoose-fa-timestamps.plu
|
|||||||
import { CronModule } from "./utils/cron/cron.module";
|
import { CronModule } from "./utils/cron/cron.module";
|
||||||
import { WorkflowStepManagementModule } from "./workflow-step-management/workflow-step-management.module";
|
import { WorkflowStepManagementModule } from "./workflow-step-management/workflow-step-management.module";
|
||||||
|
|
||||||
dotenv.config();
|
|
||||||
dotenv.config({ path: `.${process.env.NODE_ENV}.env` });
|
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
imports: [
|
imports: [
|
||||||
ConfigModule.forRoot({ isGlobal: true }),
|
ConfigModule.forRoot({ isGlobal: true }),
|
||||||
ScheduleModule.forRoot(),
|
|
||||||
CronModule,
|
CronModule,
|
||||||
ServeStaticModule.forRoot({
|
ServeStaticModule.forRoot({
|
||||||
rootPath: join(__dirname, "..", "files"),
|
rootPath: join(__dirname, "..", "files"),
|
||||||
|
|||||||
@@ -127,9 +127,7 @@ export class ClaimRequestManagementController {
|
|||||||
@ApiOperation({ deprecated: true })
|
@ApiOperation({ deprecated: true })
|
||||||
@Get("required-documents-status/:claimRequestID")
|
@Get("required-documents-status/:claimRequestID")
|
||||||
@ApiParam({ name: "claimRequestID" })
|
@ApiParam({ name: "claimRequestID" })
|
||||||
async getRequiredDocumentsStatus(
|
async getRequiredDocumentsStatus(@Param("claimRequestID") requestId: string) {
|
||||||
@Param("claimRequestID") requestId: string,
|
|
||||||
) {
|
|
||||||
return await this.claimRequestManagementService.getRequiredDocumentsStatus(
|
return await this.claimRequestManagementService.getRequiredDocumentsStatus(
|
||||||
requestId,
|
requestId,
|
||||||
);
|
);
|
||||||
@@ -293,10 +291,7 @@ export class ClaimRequestManagementController {
|
|||||||
@ApiOperation({ deprecated: true })
|
@ApiOperation({ deprecated: true })
|
||||||
@Get("request/:claimRequestId")
|
@Get("request/:claimRequestId")
|
||||||
@ApiParam({ name: "claimRequestId" })
|
@ApiParam({ name: "claimRequestId" })
|
||||||
myRequests(
|
myRequests(@Param("claimRequestId") requestId: string, @CurrentUser() user) {
|
||||||
@Param("claimRequestId") requestId: string,
|
|
||||||
@CurrentUser() user,
|
|
||||||
) {
|
|
||||||
return this.claimRequestManagementService.requestDetails(requestId, user);
|
return this.claimRequestManagementService.requestDetails(requestId, user);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -331,12 +326,12 @@ export class ClaimRequestManagementController {
|
|||||||
@UploadedFile() file: Express.Multer.File,
|
@UploadedFile() file: Express.Multer.File,
|
||||||
@CurrentUser() user,
|
@CurrentUser() user,
|
||||||
) {
|
) {
|
||||||
return await this.claimRequestManagementService.submitUserReply(
|
// return await this.claimRequestManagementService.submitUserReply(
|
||||||
requestId,
|
// requestId,
|
||||||
body,
|
// body,
|
||||||
file,
|
// file,
|
||||||
user,
|
// user,
|
||||||
);
|
// );
|
||||||
}
|
}
|
||||||
|
|
||||||
@ApiOperation({ deprecated: true })
|
@ApiOperation({ deprecated: true })
|
||||||
|
|||||||
@@ -11,7 +11,10 @@ import { ClaimRequestManagementService } from "./claim-request-management.servic
|
|||||||
import { CarGreenCardDbService } from "./entites/db-service/car-green-card.db.service";
|
import { CarGreenCardDbService } from "./entites/db-service/car-green-card.db.service";
|
||||||
import { ClaimRequestManagementDbService } from "./entites/db-service/claim-request-management.db.service";
|
import { ClaimRequestManagementDbService } from "./entites/db-service/claim-request-management.db.service";
|
||||||
import { ClaimCaseDbService } from "./entites/db-service/claim-case.db.service";
|
import { ClaimCaseDbService } from "./entites/db-service/claim-case.db.service";
|
||||||
import { ClaimCase, ClaimCaseSchema } from "./entites/schema/claim-cases.schema";
|
import {
|
||||||
|
ClaimCase,
|
||||||
|
ClaimCaseSchema,
|
||||||
|
} from "./entites/schema/claim-cases.schema";
|
||||||
import { ClaimSignDbService } from "./entites/db-service/claim-sign.db.service";
|
import { ClaimSignDbService } from "./entites/db-service/claim-sign.db.service";
|
||||||
import { DamageImageDbService } from "./entites/db-service/damage-image.db.service";
|
import { DamageImageDbService } from "./entites/db-service/damage-image.db.service";
|
||||||
import { ClaimFactorsImageDbService } from "./entites/db-service/factor-image.db.service";
|
import { ClaimFactorsImageDbService } from "./entites/db-service/factor-image.db.service";
|
||||||
@@ -47,9 +50,11 @@ import { ClientModule } from "src/client/client.module";
|
|||||||
import { ClaimAccessGuard } from "src/auth/guards/claim-access.guard";
|
import { ClaimAccessGuard } from "src/auth/guards/claim-access.guard";
|
||||||
import { JwtModule } from "@nestjs/jwt";
|
import { JwtModule } from "@nestjs/jwt";
|
||||||
import { MediaPolicyModule } from "src/media-policy/media-policy.module";
|
import { MediaPolicyModule } from "src/media-policy/media-policy.module";
|
||||||
|
import { HttpModule } from "@nestjs/axios";
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
imports: [
|
imports: [
|
||||||
|
HttpModule,
|
||||||
PublicIdModule,
|
PublicIdModule,
|
||||||
UsersModule,
|
UsersModule,
|
||||||
RequestManagementModule,
|
RequestManagementModule,
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -18,7 +18,7 @@ import { readFile } from "node:fs/promises";
|
|||||||
import { ApiBearerAuth, ApiParam, ApiTags, ApiOperation, ApiResponse, ApiBody, ApiConsumes } from "@nestjs/swagger";
|
import { ApiBearerAuth, ApiParam, ApiTags, ApiOperation, ApiResponse, ApiBody, ApiConsumes } from "@nestjs/swagger";
|
||||||
import { FileInterceptor } from "@nestjs/platform-express";
|
import { FileInterceptor } from "@nestjs/platform-express";
|
||||||
import { diskStorage } from "multer";
|
import { diskStorage } from "multer";
|
||||||
import { extname } from "path";
|
import { extname } from "node:path";
|
||||||
import { Types } from "mongoose";
|
import { Types } from "mongoose";
|
||||||
import { GlobalGuard } from "src/auth/guards/global.guard";
|
import { GlobalGuard } from "src/auth/guards/global.guard";
|
||||||
import { RolesGuard } from "src/auth/guards/role.guard";
|
import { RolesGuard } from "src/auth/guards/role.guard";
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { Prop, Schema } from "@nestjs/mongoose";
|
import { Prop, Schema } from "@nestjs/mongoose";
|
||||||
import { v4 as uuidv4 } from "uuid";
|
import { randomUUID } from "node:crypto";
|
||||||
|
|
||||||
@Schema({ versionKey: false, _id: false })
|
@Schema({ versionKey: false, _id: false })
|
||||||
export class ImageRequiredModel {
|
export class ImageRequiredModel {
|
||||||
@@ -20,7 +20,7 @@ export class ImageRequiredModel {
|
|||||||
constructor(claimFile: any[]) {
|
constructor(claimFile: any[]) {
|
||||||
this.aroundTheCar.forEach((a) => {
|
this.aroundTheCar.forEach((a) => {
|
||||||
Object.assign(a, {
|
Object.assign(a, {
|
||||||
partId: uuidv4(),
|
partId: randomUUID(),
|
||||||
imageId: null,
|
imageId: null,
|
||||||
aiReport: {},
|
aiReport: {},
|
||||||
upload: false,
|
upload: false,
|
||||||
@@ -28,7 +28,7 @@ export class ImageRequiredModel {
|
|||||||
});
|
});
|
||||||
this.selectPartOfCar = claimFile.map((c, idx) =>
|
this.selectPartOfCar = claimFile.map((c, idx) =>
|
||||||
Object.assign(c, {
|
Object.assign(c, {
|
||||||
partId: uuidv4(),
|
partId: randomUUID(),
|
||||||
aiReport: {},
|
aiReport: {},
|
||||||
imageId: null,
|
imageId: null,
|
||||||
upload: false,
|
upload: false,
|
||||||
|
|||||||
@@ -1,13 +1,9 @@
|
|||||||
import * as jMoment from "jalali-moment";
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Converts an ISO date string or Date to Jalali (Persian) date and time.
|
* Converts an ISO date string or Date to Jalali (Persian) date and time.
|
||||||
* @param input - ISO date string (e.g. 2026-02-08T13:51:20.747+00:00) or Date
|
* @param input - ISO date string (e.g. 2026-02-08T13:51:20.747+00:00) or Date
|
||||||
* @returns Tuple [dateStr, timeStr] e.g. ['1404/11/24', '13:37']
|
* @returns Tuple [dateStr, timeStr] e.g. ['1404/11/24', '13:37']
|
||||||
*/
|
*/
|
||||||
export function toJalaliDateAndTime(
|
export function toJalaliDateAndTime(input: string | Date): [string, string] {
|
||||||
input: string | Date,
|
|
||||||
): [string, string] {
|
|
||||||
const d = new Date(input);
|
const d = new Date(input);
|
||||||
const dateStr = toJalaliDate(d);
|
const dateStr = toJalaliDate(d);
|
||||||
const timeStr = toJalaliTime(d);
|
const timeStr = toJalaliTime(d);
|
||||||
@@ -32,8 +28,7 @@ export function jalaliToGregorianDate(
|
|||||||
): string | null {
|
): string | null {
|
||||||
if (input === null || input === undefined) return null;
|
if (input === null || input === undefined) return null;
|
||||||
|
|
||||||
const raw =
|
const raw = typeof input === "number" ? String(input) : String(input).trim();
|
||||||
typeof input === "number" ? String(input) : String(input).trim();
|
|
||||||
if (!raw) return null;
|
if (!raw) return null;
|
||||||
|
|
||||||
let year = 0;
|
let year = 0;
|
||||||
@@ -58,20 +53,16 @@ export function jalaliToGregorianDate(
|
|||||||
|
|
||||||
if (!year || !month || !day) return null;
|
if (!year || !month || !day) return null;
|
||||||
|
|
||||||
// If the year already looks Gregorian (>= 1900), assume the caller already
|
// If the year already looks Gregorian (>= 1900), just normalise formatting.
|
||||||
// sent a Gregorian date and just normalise the formatting.
|
|
||||||
if (year >= 1900) {
|
if (year >= 1900) {
|
||||||
const mm = String(month).padStart(2, "0");
|
const mm = String(month).padStart(2, "0");
|
||||||
const dd = String(day).padStart(2, "0");
|
const dd = String(day).padStart(2, "0");
|
||||||
const m = jMoment(`${year}-${mm}-${dd}`, "YYYY-MM-DD");
|
const d = new Date(`${year}-${mm}-${dd}`);
|
||||||
return m.isValid() ? m.format("YYYY-MM-DD") : null;
|
if (isNaN(d.getTime())) return null;
|
||||||
|
return `${year}-${mm}-${dd}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
const mm = String(month).padStart(2, "0");
|
return jalaliPartsToGregorian(year, month, day);
|
||||||
const dd = String(day).padStart(2, "0");
|
|
||||||
const jm = jMoment(`${year}-${mm}-${dd}`, "jYYYY-jMM-jDD");
|
|
||||||
if (!jm.isValid()) return null;
|
|
||||||
return jm.format("YYYY-MM-DD");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -95,6 +86,92 @@ export function toJalaliTime(d: Date): string {
|
|||||||
return `${hours}:${minutes}`;
|
return `${hours}:${minutes}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Converts Jalali year/month/day parts to a Gregorian `YYYY-MM-DD` string
|
||||||
|
* using the algorithmic conversion (no external deps).
|
||||||
|
* Returns `null` if the resulting Gregorian date is invalid.
|
||||||
|
*/
|
||||||
|
function jalaliPartsToGregorian(
|
||||||
|
jYear: number,
|
||||||
|
jMonth: number,
|
||||||
|
jDay: number,
|
||||||
|
): string | null {
|
||||||
|
// Jalali to Julian Day Number, then to Gregorian
|
||||||
|
// Algorithm: https://www.fourmilab.ch/documents/calendar/
|
||||||
|
const jy = jYear - 979;
|
||||||
|
const jm = jMonth - 1;
|
||||||
|
const jd = jDay - 1;
|
||||||
|
|
||||||
|
let jDayNo =
|
||||||
|
365 * jy + Math.floor(jy / 4) - Math.floor(jy / 100) + Math.floor(jy / 400);
|
||||||
|
|
||||||
|
const jalaliMonthDays = [31, 31, 31, 31, 31, 31, 30, 30, 30, 30, 30, 29];
|
||||||
|
for (let i = 0; i < jm; i++) {
|
||||||
|
jDayNo += jalaliMonthDays[i];
|
||||||
|
}
|
||||||
|
jDayNo += jd;
|
||||||
|
|
||||||
|
// Convert to Gregorian day number (offset from 1970-01-01 in Julian days)
|
||||||
|
const gDayNo = jDayNo + 79;
|
||||||
|
|
||||||
|
let gy = 1979 + 400 * Math.floor(gDayNo / 146097);
|
||||||
|
let days = gDayNo % 146097;
|
||||||
|
|
||||||
|
let leap = true;
|
||||||
|
if (days >= 36525) {
|
||||||
|
days--;
|
||||||
|
gy += 100 * Math.floor(days / 36524);
|
||||||
|
days %= 36524;
|
||||||
|
if (days >= 365) days++;
|
||||||
|
else leap = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
gy += 4 * Math.floor(days / 1461);
|
||||||
|
days %= 1461;
|
||||||
|
|
||||||
|
if (days >= 366) {
|
||||||
|
leap = false;
|
||||||
|
days--;
|
||||||
|
gy += Math.floor(days / 365);
|
||||||
|
days %= 365;
|
||||||
|
}
|
||||||
|
|
||||||
|
const gregorianMonthDays = [
|
||||||
|
31,
|
||||||
|
leap ? 29 : 28,
|
||||||
|
31,
|
||||||
|
30,
|
||||||
|
31,
|
||||||
|
30,
|
||||||
|
31,
|
||||||
|
31,
|
||||||
|
30,
|
||||||
|
31,
|
||||||
|
30,
|
||||||
|
31,
|
||||||
|
];
|
||||||
|
|
||||||
|
let gm = 0;
|
||||||
|
for (let i = 0; i < 12; i++) {
|
||||||
|
if (days < gregorianMonthDays[i]) {
|
||||||
|
gm = i + 1;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
days -= gregorianMonthDays[i];
|
||||||
|
}
|
||||||
|
const gd = days + 1;
|
||||||
|
|
||||||
|
const mm = String(gm).padStart(2, "0");
|
||||||
|
const dd = String(gd).padStart(2, "0");
|
||||||
|
const result = `${gy}-${mm}-${dd}`;
|
||||||
|
|
||||||
|
// Sanity check
|
||||||
|
const check = new Date(result);
|
||||||
|
if (isNaN(check.getTime())) return null;
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
const PERSIAN_TO_ENGLISH: Record<string, string> = {
|
const PERSIAN_TO_ENGLISH: Record<string, string> = {
|
||||||
"۰": "0",
|
"۰": "0",
|
||||||
"۱": "1",
|
"۱": "1",
|
||||||
|
|||||||
58
src/main.ts
58
src/main.ts
@@ -1,8 +1,8 @@
|
|||||||
import { NestFactory } from "@nestjs/core";
|
import { NestFactory } from "@nestjs/core";
|
||||||
import { NestExpressApplication } from "@nestjs/platform-express";
|
import { NestExpressApplication } from "@nestjs/platform-express";
|
||||||
import { DocumentBuilder, SwaggerModule } from "@nestjs/swagger";
|
import { DocumentBuilder, SwaggerModule } from "@nestjs/swagger";
|
||||||
import * as basicAuth from "express-basic-auth";
|
|
||||||
import { AppModule } from "./app.module";
|
import { AppModule } from "./app.module";
|
||||||
|
import { ConfigService } from "@nestjs/config";
|
||||||
|
|
||||||
async function bootstrap() {
|
async function bootstrap() {
|
||||||
const app = await NestFactory.create<NestExpressApplication>(AppModule, {
|
const app = await NestFactory.create<NestExpressApplication>(AppModule, {
|
||||||
@@ -15,15 +15,46 @@ async function bootstrap() {
|
|||||||
allowedHeaders: "Content-Type, Authorization",
|
allowedHeaders: "Content-Type, Authorization",
|
||||||
});
|
});
|
||||||
|
|
||||||
app.use(
|
const configService = app.get(ConfigService);
|
||||||
["/docs"],
|
|
||||||
basicAuth({
|
const isDevelopment = configService.get<string>("NODE_ENV") === "development";
|
||||||
challenge: true,
|
|
||||||
users: {
|
if (isDevelopment) {
|
||||||
[process.env.SWAGGER_USER]: process.env.SWAGGER_PASSWORD,
|
app.use("/docs", (req, res, next) => {
|
||||||
},
|
const authHeader = req.headers.authorization;
|
||||||
}),
|
|
||||||
);
|
const challenge = () => {
|
||||||
|
res.setHeader("WWW-Authenticate", 'Basic realm="Swagger"');
|
||||||
|
res.status(401).send("Unauthorized");
|
||||||
|
};
|
||||||
|
|
||||||
|
if (!authHeader?.startsWith("Basic ")) {
|
||||||
|
return challenge();
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const base64 = authHeader.split(" ")[1];
|
||||||
|
const [username, ...rest] = Buffer.from(base64, "base64")
|
||||||
|
.toString("utf8")
|
||||||
|
.split(":");
|
||||||
|
const password = rest.join(":");
|
||||||
|
|
||||||
|
const expectedUser = configService.get<string>("SWAGGER_USER", "");
|
||||||
|
const expectedPassword = configService.get<string>(
|
||||||
|
"SWAGGER_PASSWORD",
|
||||||
|
"",
|
||||||
|
);
|
||||||
|
|
||||||
|
if (username !== expectedUser || password !== expectedPassword) {
|
||||||
|
return challenge();
|
||||||
|
}
|
||||||
|
|
||||||
|
next();
|
||||||
|
} catch {
|
||||||
|
return challenge();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
const config = new DocumentBuilder()
|
const config = new DocumentBuilder()
|
||||||
.setTitle("yara724-backend")
|
.setTitle("yara724-backend")
|
||||||
@@ -33,11 +64,15 @@ async function bootstrap() {
|
|||||||
.addServer("http://localhost:9001")
|
.addServer("http://localhost:9001")
|
||||||
.addBearerAuth()
|
.addBearerAuth()
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
const docs = SwaggerModule.createDocument(app, config);
|
const docs = SwaggerModule.createDocument(app, config);
|
||||||
SwaggerModule.setup("/docs", app, docs, {
|
|
||||||
|
SwaggerModule.setup("docs", app, docs, {
|
||||||
swaggerOptions: {
|
swaggerOptions: {
|
||||||
persistAuthorization: true,
|
persistAuthorization: true,
|
||||||
docExpansion: "none",
|
docExpansion: "none",
|
||||||
|
ui: isDevelopment ? true : false,
|
||||||
|
raw: isDevelopment ? true : false,
|
||||||
tagsSorter: (a: string, b: string) => {
|
tagsSorter: (a: string, b: string) => {
|
||||||
const priority: Record<string, number> = {
|
const priority: Record<string, number> = {
|
||||||
user: 0,
|
user: 0,
|
||||||
@@ -50,6 +85,7 @@ async function bootstrap() {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
await app.listen(process.env.PORT);
|
await app.listen(process.env.PORT);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -9,12 +9,12 @@ import {
|
|||||||
ServiceUnavailableException,
|
ServiceUnavailableException,
|
||||||
UnauthorizedException,
|
UnauthorizedException,
|
||||||
} from "@nestjs/common";
|
} from "@nestjs/common";
|
||||||
import axios from "axios";
|
|
||||||
import { SandHubDbService } from "src/sand-hub/entity/db-service/sand-hub.db.service";
|
import { SandHubDbService } from "src/sand-hub/entity/db-service/sand-hub.db.service";
|
||||||
import { SandHubModel } from "src/sand-hub/entity/schema/sand-hub.schema";
|
import { SandHubModel } from "src/sand-hub/entity/schema/sand-hub.schema";
|
||||||
import { SystemSettingsService } from "src/system-settings/system-settings.service";
|
import { SystemSettingsService } from "src/system-settings/system-settings.service";
|
||||||
import { SandHubDetailDto } from "./dto/sand-hub.dto";
|
import { SandHubDetailDto } from "./dto/sand-hub.dto";
|
||||||
import { jalaliToGregorianDate } from "src/helpers/date-jalali";
|
import { jalaliToGregorianDate } from "src/helpers/date-jalali";
|
||||||
|
import { firstValueFrom } from "rxjs";
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class SandHubService {
|
export class SandHubService {
|
||||||
@@ -123,8 +123,7 @@ export class SandHubService {
|
|||||||
const u = (url || "").toLowerCase();
|
const u = (url || "").toLowerCase();
|
||||||
if (u.includes("personal-inquiry")) {
|
if (u.includes("personal-inquiry")) {
|
||||||
const p = payload as { nationalCode?: string } | undefined;
|
const p = payload as { nationalCode?: string } | undefined;
|
||||||
const nin =
|
const nin = typeof p?.nationalCode === "string" ? p.nationalCode : "-";
|
||||||
typeof p?.nationalCode === "string" ? p.nationalCode : "-";
|
|
||||||
return {
|
return {
|
||||||
message: "success",
|
message: "success",
|
||||||
data: {
|
data: {
|
||||||
@@ -182,12 +181,11 @@ export class SandHubService {
|
|||||||
);
|
);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await this.httpService.axiosRef.post(
|
const response = await firstValueFrom(
|
||||||
process.env.SANDHUB_URL_LOGIN,
|
this.httpService.post(process.env.SANDHUB_URL_LOGIN, {
|
||||||
{
|
|
||||||
email: process.env.SANDHUB_USERNAME,
|
email: process.env.SANDHUB_USERNAME,
|
||||||
password: process.env.SANDHUB_PASSWORD,
|
password: process.env.SANDHUB_PASSWORD,
|
||||||
},
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
const token = response.data.accessToken;
|
const token = response.data.accessToken;
|
||||||
@@ -237,16 +235,18 @@ export class SandHubService {
|
|||||||
);
|
);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await this.httpService.axiosRef.post(
|
const response = await firstValueFrom(
|
||||||
`${baseUrl}/user/login`,
|
this.httpService.post(
|
||||||
{ email, password },
|
`${baseUrl}/user/login`,
|
||||||
{
|
{ email, password },
|
||||||
headers: {
|
{
|
||||||
Accept: "*/*",
|
headers: {
|
||||||
"Content-Type": "application/json",
|
Accept: "*/*",
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
},
|
||||||
|
timeout: 30000,
|
||||||
},
|
},
|
||||||
timeout: 30000,
|
),
|
||||||
},
|
|
||||||
);
|
);
|
||||||
|
|
||||||
const token = response.data?.accessToken;
|
const token = response.data?.accessToken;
|
||||||
@@ -260,7 +260,10 @@ export class SandHubService {
|
|||||||
|
|
||||||
return this.tejaratAccessToken;
|
return this.tejaratAccessToken;
|
||||||
} catch (er) {
|
} catch (er) {
|
||||||
this.logger.error("Failed to login to Tejarat inquiry:", er?.message || er);
|
this.logger.error(
|
||||||
|
"Failed to login to Tejarat inquiry:",
|
||||||
|
er?.message || er,
|
||||||
|
);
|
||||||
this.tejaratAccessToken = null;
|
this.tejaratAccessToken = null;
|
||||||
this.tejaratTokenExpiry = null;
|
this.tejaratTokenExpiry = null;
|
||||||
throw new UnauthorizedException("Tejarat inquiry authentication failed");
|
throw new UnauthorizedException("Tejarat inquiry authentication failed");
|
||||||
@@ -278,14 +281,16 @@ export class SandHubService {
|
|||||||
for (let attempt = 0; attempt < maxRetries; attempt++) {
|
for (let attempt = 0; attempt < maxRetries; attempt++) {
|
||||||
const token = await this.getTejaratAccessToken();
|
const token = await this.getTejaratAccessToken();
|
||||||
try {
|
try {
|
||||||
const response = await axios.post(url, payload, {
|
const response = await firstValueFrom(
|
||||||
headers: {
|
this.httpService.post(url, payload, {
|
||||||
Authorization: `Bearer ${token}`,
|
headers: {
|
||||||
"Content-Type": "application/json",
|
Authorization: `Bearer ${token}`,
|
||||||
Accept: "application/json",
|
"Content-Type": "application/json",
|
||||||
},
|
Accept: "application/json",
|
||||||
timeout: 30000,
|
},
|
||||||
});
|
timeout: 30000,
|
||||||
|
}),
|
||||||
|
);
|
||||||
if (!response?.data) throw new Error("EMPTY_RESPONSE");
|
if (!response?.data) throw new Error("EMPTY_RESPONSE");
|
||||||
return response.data;
|
return response.data;
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
@@ -361,14 +366,16 @@ export class SandHubService {
|
|||||||
|
|
||||||
for (let attempt = 0; attempt < maxRetries; attempt++) {
|
for (let attempt = 0; attempt < maxRetries; attempt++) {
|
||||||
try {
|
try {
|
||||||
const response = await axios.post(url, payload, {
|
const response = await firstValueFrom(
|
||||||
headers: {
|
this.httpService.post(url, payload, {
|
||||||
Authorization: `Bearer ${token}`,
|
headers: {
|
||||||
"Content-Type": "application/json",
|
Authorization: `Bearer ${token}`,
|
||||||
Accept: "application/json",
|
"Content-Type": "application/json",
|
||||||
},
|
Accept: "application/json",
|
||||||
timeout: 50000,
|
},
|
||||||
});
|
timeout: 50000,
|
||||||
|
}),
|
||||||
|
);
|
||||||
if (!response?.data) throw new Error("EMPTY_RESPONSE");
|
if (!response?.data) throw new Error("EMPTY_RESPONSE");
|
||||||
return response.data;
|
return response.data;
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
@@ -394,53 +401,67 @@ export class SandHubService {
|
|||||||
// Company information
|
// Company information
|
||||||
CompanyCode: newResponse.companyId || newResponse.CompanyCode,
|
CompanyCode: newResponse.companyId || newResponse.CompanyCode,
|
||||||
CompanyName: newResponse.companyPersianName || newResponse.CompanyName,
|
CompanyName: newResponse.companyPersianName || newResponse.CompanyName,
|
||||||
|
|
||||||
// Vehicle information
|
// Vehicle information
|
||||||
MapTypNam: newResponse.vehiclePersianName || newResponse.MapTypNam,
|
MapTypNam: newResponse.vehiclePersianName || newResponse.MapTypNam,
|
||||||
UsageField: newResponse.persianCarType || newResponse.UsageField || (newResponse.usgCod === "8" ? "شخصی" : "نامشخص"),
|
UsageField:
|
||||||
|
newResponse.persianCarType ||
|
||||||
|
newResponse.UsageField ||
|
||||||
|
(newResponse.usgCod === "8" ? "شخصی" : "نامشخص"),
|
||||||
MapUsageName: newResponse.MapUsageName || newResponse.MapUsageName,
|
MapUsageName: newResponse.MapUsageName || newResponse.MapUsageName,
|
||||||
|
|
||||||
// Financial coverage
|
// Financial coverage
|
||||||
FinancialCvrCptl: newResponse.financeCoverage || newResponse.FinancialCvrCptl || "0",
|
FinancialCvrCptl:
|
||||||
|
newResponse.financeCoverage || newResponse.FinancialCvrCptl || "0",
|
||||||
|
|
||||||
// Dates
|
// Dates
|
||||||
IssueDate: newResponse.persianStartDate || newResponse.IssueDate,
|
IssueDate: newResponse.persianStartDate || newResponse.IssueDate,
|
||||||
EndDate: newResponse.persianEndDate || newResponse.EndDate,
|
EndDate: newResponse.persianEndDate || newResponse.EndDate,
|
||||||
|
|
||||||
// Insurance details
|
// Insurance details
|
||||||
LastCompanyDocumentNumber: newResponse.lastCompanyInsuranceNumber || newResponse.LastCompanyDocumentNumber || newResponse.insuranceNumber,
|
LastCompanyDocumentNumber:
|
||||||
|
newResponse.lastCompanyInsuranceNumber ||
|
||||||
|
newResponse.LastCompanyDocumentNumber ||
|
||||||
|
newResponse.insuranceNumber,
|
||||||
|
|
||||||
// Technical details
|
// Technical details
|
||||||
MtrNum: newResponse.MtrNum || newResponse.mtrnum,
|
MtrNum: newResponse.MtrNum || newResponse.mtrnum,
|
||||||
ShsNum: newResponse.ShsNum || newResponse.shsNam || newResponse.ChassisNumberField,
|
ShsNum:
|
||||||
|
newResponse.ShsNum ||
|
||||||
|
newResponse.shsNam ||
|
||||||
|
newResponse.ChassisNumberField,
|
||||||
vin: newResponse.vin || newResponse.VinNumberField || newResponse.vin,
|
vin: newResponse.vin || newResponse.VinNumberField || newResponse.vin,
|
||||||
VinNumberField: newResponse.vin || newResponse.VinNumberField,
|
VinNumberField: newResponse.vin || newResponse.VinNumberField,
|
||||||
ChassisNumberField: newResponse.ChassisNumberField || newResponse.shsNam,
|
ChassisNumberField: newResponse.ChassisNumberField || newResponse.shsNam,
|
||||||
EngineNumberField: newResponse.EngineNumberField || newResponse.mtrnum,
|
EngineNumberField: newResponse.EngineNumberField || newResponse.mtrnum,
|
||||||
ModelField: newResponse.ModelField || newResponse.ModelField,
|
ModelField: newResponse.ModelField || newResponse.ModelField,
|
||||||
|
|
||||||
// Discount information
|
// Discount information
|
||||||
DisFnYrPrcnt: newResponse.DisFnYrPrcnt || newResponse.disFnYrPrcnt || "0",
|
DisFnYrPrcnt: newResponse.DisFnYrPrcnt || newResponse.disFnYrPrcnt || "0",
|
||||||
DisLfYrPrcnt: newResponse.DisLfYrPrcnt || newResponse.disLfYrPrcnt || "0",
|
DisLfYrPrcnt: newResponse.DisLfYrPrcnt || newResponse.disLfYrPrcnt || "0",
|
||||||
DisPrsnYrPrcnt: newResponse.DisPrsnYrPrcnt || newResponse.disPrsnYrPrcnt || "0",
|
DisPrsnYrPrcnt:
|
||||||
|
newResponse.DisPrsnYrPrcnt || newResponse.disPrsnYrPrcnt || "0",
|
||||||
DisFnYrNum: newResponse.DisFnYrNum || newResponse.disFnYrNum,
|
DisFnYrNum: newResponse.DisFnYrNum || newResponse.disFnYrNum,
|
||||||
DisLfYrNum: newResponse.DisLfYrNum || newResponse.disLfYrNum,
|
DisLfYrNum: newResponse.DisLfYrNum || newResponse.disLfYrNum,
|
||||||
DisPrsnYrNum: newResponse.DisPrsnYrNum || newResponse.disPrsnYrNum,
|
DisPrsnYrNum: newResponse.DisPrsnYrNum || newResponse.disPrsnYrNum,
|
||||||
|
|
||||||
// Usage and vehicle system codes
|
// Usage and vehicle system codes
|
||||||
UsageCode: newResponse.UsageCode || newResponse.usgCod,
|
UsageCode: newResponse.UsageCode || newResponse.usgCod,
|
||||||
VehicleSystemCode: newResponse.VehicleSystemCode || newResponse.vehSysCod,
|
VehicleSystemCode: newResponse.VehicleSystemCode || newResponse.vehSysCod,
|
||||||
CarGroupCode: newResponse.CarGroupCode || newResponse.carGrpCod,
|
CarGroupCode: newResponse.CarGroupCode || newResponse.carGrpCod,
|
||||||
|
|
||||||
// Policy information
|
// Policy information
|
||||||
ThirdPolicyCode: newResponse.ThirdPolicyCode || newResponse.ThirdPolicyCode,
|
ThirdPolicyCode:
|
||||||
|
newResponse.ThirdPolicyCode || newResponse.ThirdPolicyCode,
|
||||||
|
|
||||||
// Endorsement data
|
// Endorsement data
|
||||||
EdrsJson: newResponse.EdrsJson || (newResponse.edrSes ? JSON.stringify(newResponse.edrSes) : undefined),
|
EdrsJson:
|
||||||
|
newResponse.EdrsJson ||
|
||||||
|
(newResponse.edrSes ? JSON.stringify(newResponse.edrSes) : undefined),
|
||||||
|
|
||||||
// Insurance fullname
|
// Insurance fullname
|
||||||
InsuranceFullName: newResponse.InsuranceFullName || newResponse.fullname,
|
InsuranceFullName: newResponse.InsuranceFullName || newResponse.fullname,
|
||||||
|
|
||||||
// National code
|
// National code
|
||||||
nationalCode: newResponse.nationalCode || newResponse.ntnlId,
|
nationalCode: newResponse.nationalCode || newResponse.ntnlId,
|
||||||
};
|
};
|
||||||
@@ -486,10 +507,7 @@ export class SandHubService {
|
|||||||
* this codebase has the value as a Jalali date (number `13770624` or string
|
* this codebase has the value as a Jalali date (number `13770624` or string
|
||||||
* `"1377-06-24"`/`"1377/06/24"`). We convert here so callers don't have to.
|
* `"1377-06-24"`/`"1377/06/24"`). We convert here so callers don't have to.
|
||||||
*/
|
*/
|
||||||
async getPersonalInquiry(
|
async getPersonalInquiry(nationalCode: string, birthDate: number | string) {
|
||||||
nationalCode: string,
|
|
||||||
birthDate: number | string,
|
|
||||||
) {
|
|
||||||
try {
|
try {
|
||||||
const requestUrl = `${process.env.SANDHUB_BASE_URL}/personal-inquiry/tejarat-no`;
|
const requestUrl = `${process.env.SANDHUB_BASE_URL}/personal-inquiry/tejarat-no`;
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { KavenegarService } from "@fraybabak/kavenegar_nest";
|
|
||||||
import { Injectable, Logger } from "@nestjs/common";
|
import { Injectable, Logger } from "@nestjs/common";
|
||||||
|
import { KavenegarService } from "./kavenegar.service";
|
||||||
import {
|
import {
|
||||||
isKavenegarSuccess,
|
isKavenegarSuccess,
|
||||||
KavenegarNormalized,
|
KavenegarNormalized,
|
||||||
@@ -21,29 +21,27 @@ export class KavenegarSmsGateway {
|
|||||||
|
|
||||||
async sendMessage(data: SendMessage) {
|
async sendMessage(data: SendMessage) {
|
||||||
try {
|
try {
|
||||||
const body = await this.sender.Send({
|
const body = await this.sender.send({
|
||||||
sender: "10008663",
|
sender: "10008663",
|
||||||
...data,
|
...data,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!isKavenegarSuccess(body)) {
|
if (!isKavenegarSuccess(body)) {
|
||||||
const normalized = normalizeKavenegarBody(body);
|
const normalized = normalizeKavenegarBody(body);
|
||||||
|
|
||||||
this.logProviderRejection("send", data.receptor, undefined, normalized);
|
this.logProviderRejection("send", data.receptor, undefined, normalized);
|
||||||
|
|
||||||
throw new SmsProviderException("send", {
|
throw new SmsProviderException("send", {
|
||||||
receptor: data.receptor,
|
receptor: data.receptor,
|
||||||
providerBody: body,
|
providerBody: body,
|
||||||
normalized,
|
normalized,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
this.logger.log(
|
|
||||||
`Kavenegar Send ok receptor=${data.receptor} status=200`,
|
|
||||||
);
|
|
||||||
return body;
|
return body;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
if (e instanceof SmsProviderException) throw e;
|
if (e instanceof SmsProviderException) throw e;
|
||||||
const detail = safeJsonStringify(unwrapKavenegarTransportError(e));
|
|
||||||
this.logger.error(
|
|
||||||
`Kavenegar Send transport error receptor=${data.receptor} detail=${detail}`,
|
|
||||||
);
|
|
||||||
throw new SmsTransportException("send", { receptor: data.receptor }, e);
|
throw new SmsTransportException("send", { receptor: data.receptor }, e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -51,14 +49,17 @@ export class KavenegarSmsGateway {
|
|||||||
async verifyLookUp(data: VerifyLookUpMessage) {
|
async verifyLookUp(data: VerifyLookUpMessage) {
|
||||||
try {
|
try {
|
||||||
const body = await this.sender.verifyLookup(data);
|
const body = await this.sender.verifyLookup(data);
|
||||||
|
|
||||||
if (!isKavenegarSuccess(body)) {
|
if (!isKavenegarSuccess(body)) {
|
||||||
const normalized = normalizeKavenegarBody(body);
|
const normalized = normalizeKavenegarBody(body);
|
||||||
|
|
||||||
this.logProviderRejection(
|
this.logProviderRejection(
|
||||||
"verifyLookUp",
|
"verifyLookUp",
|
||||||
data.receptor,
|
data.receptor,
|
||||||
data.template,
|
data.template,
|
||||||
normalized,
|
normalized,
|
||||||
);
|
);
|
||||||
|
|
||||||
throw new SmsProviderException("verifyLookup", {
|
throw new SmsProviderException("verifyLookup", {
|
||||||
receptor: data.receptor,
|
receptor: data.receptor,
|
||||||
template: data.template,
|
template: data.template,
|
||||||
@@ -66,19 +67,21 @@ export class KavenegarSmsGateway {
|
|||||||
normalized,
|
normalized,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
this.logger.log(
|
|
||||||
`Kavenegar verifyLookUp ok receptor=${data.receptor} template=${data.template} status=200`,
|
|
||||||
);
|
|
||||||
return body;
|
return body;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
if (e instanceof SmsProviderException) throw e;
|
if (e instanceof SmsProviderException) throw e;
|
||||||
|
|
||||||
const detail = safeJsonStringify(unwrapKavenegarTransportError(e));
|
const detail = safeJsonStringify(unwrapKavenegarTransportError(e));
|
||||||
this.logger.error(
|
|
||||||
`Kavenegar verifyLookUp transport error receptor=${data.receptor} template=${data.template} detail=${detail}`,
|
this.logger.error(`Kavenegar transport error ${detail}`);
|
||||||
);
|
|
||||||
throw new SmsTransportException(
|
throw new SmsTransportException(
|
||||||
"verifyLookup",
|
"verifyLookup",
|
||||||
{ receptor: data.receptor, template: data.template },
|
{
|
||||||
|
receptor: data.receptor,
|
||||||
|
template: data.template,
|
||||||
|
},
|
||||||
e,
|
e,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -90,9 +93,10 @@ export class KavenegarSmsGateway {
|
|||||||
template: string | undefined,
|
template: string | undefined,
|
||||||
normalized: KavenegarNormalized,
|
normalized: KavenegarNormalized,
|
||||||
) {
|
) {
|
||||||
const t = template != null && template !== "" ? ` template=${template}` : "";
|
const t = template != null ? ` template=${template}` : "";
|
||||||
|
|
||||||
this.logger.warn(
|
this.logger.warn(
|
||||||
`Kavenegar ${op} rejected receptor=${receptor}${t} providerStatus=${normalized.httpLikeStatus ?? "unknown"} providerMessage=${normalized.message ?? "n/a"} body=${normalized.raw}`,
|
`Kavenegar ${op} rejected receptor=${receptor}${t} providerStatus=${normalized.httpLikeStatus}`,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
45
src/sms-orchestration/provider/kavenegar.service.ts
Normal file
45
src/sms-orchestration/provider/kavenegar.service.ts
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
import { HttpService } from "@nestjs/axios";
|
||||||
|
import { Injectable } from "@nestjs/common";
|
||||||
|
import { ConfigService } from "@nestjs/config";
|
||||||
|
import { firstValueFrom } from "rxjs";
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class KavenegarService {
|
||||||
|
private readonly apiKey: string;
|
||||||
|
private readonly baseUrl: string;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
private readonly http: HttpService,
|
||||||
|
private readonly configService: ConfigService,
|
||||||
|
) {
|
||||||
|
this.apiKey = this.configService.get<string>("SMS_API_KEY", "");
|
||||||
|
|
||||||
|
this.baseUrl = `https://api.kavenegar.com/v1/${this.apiKey}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
async send(data: { receptor: string; message: string; sender?: string }) {
|
||||||
|
const response = await firstValueFrom(
|
||||||
|
this.http.post(`${this.baseUrl}/sms/send.json`, null, {
|
||||||
|
params: data,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
return response.data;
|
||||||
|
}
|
||||||
|
|
||||||
|
async verifyLookup(data: {
|
||||||
|
receptor: string;
|
||||||
|
token: string;
|
||||||
|
template: string;
|
||||||
|
token2?: string;
|
||||||
|
token3?: string;
|
||||||
|
}) {
|
||||||
|
const response = await firstValueFrom(
|
||||||
|
this.http.get(`${this.baseUrl}/verify/lookup.json`, {
|
||||||
|
params: data,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
return response.data;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,16 +1,20 @@
|
|||||||
import { Injectable, Logger } from "@nestjs/common";
|
import { Injectable, Logger } from "@nestjs/common";
|
||||||
import axios, { isAxiosError } from "axios";
|
import { isAxiosError } from "axios";
|
||||||
import { buildParsianTemplateMessage } from "./parsian-template-messages";
|
import { buildParsianTemplateMessage } from "./parsian-template-messages";
|
||||||
import {
|
import {
|
||||||
SmsProviderException,
|
SmsProviderException,
|
||||||
SmsTransportException,
|
SmsTransportException,
|
||||||
} from "./sms-provider.exception";
|
} from "./sms-provider.exception";
|
||||||
import { SendMessage, VerifyLookUpMessage } from "./sms-gateway.types";
|
import { SendMessage, VerifyLookUpMessage } from "./sms-gateway.types";
|
||||||
|
import { firstValueFrom } from "rxjs";
|
||||||
|
import { HttpService } from "@nestjs/axios";
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class ParsianSmsGateway {
|
export class ParsianSmsGateway {
|
||||||
private readonly logger = new Logger(ParsianSmsGateway.name);
|
private readonly logger = new Logger(ParsianSmsGateway.name);
|
||||||
|
|
||||||
|
constructor(private readonly httpService: HttpService) {}
|
||||||
|
|
||||||
async sendMessage(data: SendMessage) {
|
async sendMessage(data: SendMessage) {
|
||||||
try {
|
try {
|
||||||
const body = await this.requestSend(data.receptor, data.message);
|
const body = await this.requestSend(data.receptor, data.message);
|
||||||
@@ -19,7 +23,11 @@ export class ParsianSmsGateway {
|
|||||||
} catch (e) {
|
} catch (e) {
|
||||||
if (e instanceof SmsProviderException) throw e;
|
if (e instanceof SmsProviderException) throw e;
|
||||||
const detail = isAxiosError(e)
|
const detail = isAxiosError(e)
|
||||||
? { status: e.response?.status, data: e.response?.data, message: e.message }
|
? {
|
||||||
|
status: e.response?.status,
|
||||||
|
data: e.response?.data,
|
||||||
|
message: e.message,
|
||||||
|
}
|
||||||
: e;
|
: e;
|
||||||
this.logger.error(
|
this.logger.error(
|
||||||
`Parsian Send transport error receptor=${data.receptor} detail=${JSON.stringify(detail)}`,
|
`Parsian Send transport error receptor=${data.receptor} detail=${JSON.stringify(detail)}`,
|
||||||
@@ -39,7 +47,11 @@ export class ParsianSmsGateway {
|
|||||||
} catch (e) {
|
} catch (e) {
|
||||||
if (e instanceof SmsProviderException) throw e;
|
if (e instanceof SmsProviderException) throw e;
|
||||||
const detail = isAxiosError(e)
|
const detail = isAxiosError(e)
|
||||||
? { status: e.response?.status, data: e.response?.data, message: e.message }
|
? {
|
||||||
|
status: e.response?.status,
|
||||||
|
data: e.response?.data,
|
||||||
|
message: e.message,
|
||||||
|
}
|
||||||
: e;
|
: e;
|
||||||
this.logger.error(
|
this.logger.error(
|
||||||
`Parsian verifyLookUp transport error receptor=${data.receptor} template=${data.template} detail=${JSON.stringify(detail)}`,
|
`Parsian verifyLookUp transport error receptor=${data.receptor} template=${data.template} detail=${JSON.stringify(detail)}`,
|
||||||
@@ -60,7 +72,10 @@ export class ParsianSmsGateway {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
private async requestSend(receptor: string, message: string): Promise<unknown> {
|
private async requestSend(
|
||||||
|
receptor: string,
|
||||||
|
message: string,
|
||||||
|
): Promise<unknown> {
|
||||||
const baseUrl = process.env.PARSIAN_SMS_URL;
|
const baseUrl = process.env.PARSIAN_SMS_URL;
|
||||||
if (!baseUrl?.trim()) {
|
if (!baseUrl?.trim()) {
|
||||||
throw new SmsTransportException(
|
throw new SmsTransportException(
|
||||||
@@ -71,7 +86,9 @@ export class ParsianSmsGateway {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const url = `${baseUrl}=${receptor}&Message=${encodeURIComponent(message)}`;
|
const url = `${baseUrl}=${receptor}&Message=${encodeURIComponent(message)}`;
|
||||||
const response = await axios.get(url, { headers: this.parsianHeaders() });
|
const response = await firstValueFrom(
|
||||||
|
this.httpService.get(url, { headers: this.parsianHeaders() }),
|
||||||
|
);
|
||||||
|
|
||||||
if (response.status < 200 || response.status >= 300) {
|
if (response.status < 200 || response.status >= 300) {
|
||||||
throw new SmsProviderException("send", {
|
throw new SmsProviderException("send", {
|
||||||
|
|||||||
@@ -1,20 +1,16 @@
|
|||||||
import { KavenegarModule } from "@fraybabak/kavenegar_nest";
|
import { HttpModule } from "@nestjs/axios";
|
||||||
import { Module } from "@nestjs/common";
|
import { Module } from "@nestjs/common";
|
||||||
import * as dotenv from "dotenv";
|
import { ConfigModule } from "@nestjs/config";
|
||||||
|
|
||||||
|
import { KavenegarService } from "./kavenegar.service";
|
||||||
import { KavenegarSmsGateway } from "./kavenegar-sms.gateway";
|
import { KavenegarSmsGateway } from "./kavenegar-sms.gateway";
|
||||||
import { ParsianSmsGateway } from "./parsian-sms.gateway";
|
import { ParsianSmsGateway } from "./parsian-sms.gateway";
|
||||||
import { SmsGatewayService } from "./sms-gateway.service";
|
import { SmsGatewayService } from "./sms-gateway.service";
|
||||||
|
|
||||||
dotenv.config();
|
|
||||||
dotenv.config({ path: `.${process.env.NODE_ENV}.env` });
|
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
imports: [
|
imports: [HttpModule, ConfigModule],
|
||||||
KavenegarModule.forRoot({
|
|
||||||
apikey: process.env.SMS_API_KEY || "",
|
|
||||||
}),
|
|
||||||
],
|
|
||||||
providers: [
|
providers: [
|
||||||
|
KavenegarService,
|
||||||
KavenegarSmsGateway,
|
KavenegarSmsGateway,
|
||||||
ParsianSmsGateway,
|
ParsianSmsGateway,
|
||||||
SmsGatewayService,
|
SmsGatewayService,
|
||||||
|
|||||||
@@ -5,9 +5,11 @@ import { SmsText, SmsTextSchema } from "./entities/schema/sms-text.schema";
|
|||||||
import { SmsGatewayModule } from "./provider/sms-gateway.module";
|
import { SmsGatewayModule } from "./provider/sms-gateway.module";
|
||||||
import { OtpGeneratorService } from "./otp-generator.service";
|
import { OtpGeneratorService } from "./otp-generator.service";
|
||||||
import { SmsOrchestrationService } from "./sms-orchestration.service";
|
import { SmsOrchestrationService } from "./sms-orchestration.service";
|
||||||
|
import { HttpModule } from "@nestjs/axios";
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
imports: [
|
imports: [
|
||||||
|
HttpModule,
|
||||||
SmsGatewayModule,
|
SmsGatewayModule,
|
||||||
MongooseModule.forFeature([{ name: SmsText.name, schema: SmsTextSchema }]),
|
MongooseModule.forFeature([{ name: SmsText.name, schema: SmsTextSchema }]),
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -1,95 +1,94 @@
|
|||||||
import { Injectable, Logger } from "@nestjs/common";
|
import { Injectable } from "@nestjs/common";
|
||||||
import { Cron, CronExpression } from "@nestjs/schedule";
|
|
||||||
import { RequestManagementDbService } from "src/request-management/entities/db-service/request-management.db.service";
|
import { RequestManagementDbService } from "src/request-management/entities/db-service/request-management.db.service";
|
||||||
import { SubmitReply } from "src/request-management/entities/schema/request-management.schema";
|
import { SubmitReply } from "src/request-management/entities/schema/request-management.schema";
|
||||||
import { ReqBlameStatus } from "src/Types&Enums/blame-request-management/status.enum";
|
import { ReqBlameStatus } from "src/Types&Enums/blame-request-management/status.enum";
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class AutoCloseRequestService {
|
export class AutoCloseRequestService {
|
||||||
private readonly logger = new Logger(AutoCloseRequestService.name);
|
// private readonly logger = new Logger(AutoCloseRequestService.name);
|
||||||
|
|
||||||
constructor(private readonly requestDb: RequestManagementDbService) {}
|
// constructor(private readonly requestDb: RequestManagementDbService) {}
|
||||||
|
|
||||||
async scheduleAutoClose(params: { requestId: string; runAt: Date }) {
|
// async scheduleAutoClose(params: { requestId: string; runAt: Date }) {
|
||||||
await this.requestDb.findAndUpdate(
|
// await this.requestDb.findAndUpdate(
|
||||||
{ _id: params.requestId },
|
// { _id: params.requestId },
|
||||||
{ $set: { autoCloseTriggerTime: params.runAt } },
|
// { $set: { autoCloseTriggerTime: params.runAt } },
|
||||||
);
|
// );
|
||||||
this.logger.log(
|
// this.logger.log(
|
||||||
`Scheduled auto-close for ${params.requestId} at ${params.runAt.toISOString()}`,
|
// `Scheduled auto-close for ${params.requestId} at ${params.runAt.toISOString()}`,
|
||||||
);
|
// );
|
||||||
}
|
// }
|
||||||
|
|
||||||
// Runs every hour
|
// // Runs every hour
|
||||||
@Cron(CronExpression.EVERY_HOUR)
|
// @Cron(CronExpression.EVERY_HOUR)
|
||||||
async handleAutoClose() {
|
// async handleAutoClose() {
|
||||||
const now = new Date();
|
// const now = new Date();
|
||||||
const threshold = new Date(now.getTime() - 24 * 60 * 60 * 1000);
|
// const threshold = new Date(now.getTime() - 24 * 60 * 60 * 1000);
|
||||||
|
|
||||||
const requestsToClose = await this.requestDb.findAll({
|
// const requestsToClose = await this.requestDb.findAll({
|
||||||
blameStatus: { $ne: ReqBlameStatus.CloseRequest },
|
// blameStatus: { $ne: ReqBlameStatus.CloseRequest },
|
||||||
$or: [
|
// $or: [
|
||||||
{ "expertSubmitReply.firstPartyComment.isAccept": true },
|
// { "expertSubmitReply.firstPartyComment.isAccept": true },
|
||||||
{ "expertSubmitReply.secondPartyComment.isAccept": true },
|
// { "expertSubmitReply.secondPartyComment.isAccept": true },
|
||||||
{ "expertSubmitReplyFinal.firstPartyComment.isAccept": true },
|
// { "expertSubmitReplyFinal.firstPartyComment.isAccept": true },
|
||||||
{ "expertSubmitReplyFinal.secondPartyComment.isAccept": true },
|
// { "expertSubmitReplyFinal.secondPartyComment.isAccept": true },
|
||||||
],
|
// ],
|
||||||
autoCloseTriggerTime: { $lte: threshold },
|
// autoCloseTriggerTime: { $lte: threshold },
|
||||||
});
|
// });
|
||||||
|
|
||||||
for (const request of requestsToClose) {
|
// for (const request of requestsToClose) {
|
||||||
const reply = request.expertSubmitReplyFinal || request.expertSubmitReply;
|
// const reply = request.expertSubmitReplyFinal || request.expertSubmitReply;
|
||||||
|
|
||||||
const isSubmitReply = (reply: any): reply is SubmitReply =>
|
// const isSubmitReply = (reply: any): reply is SubmitReply =>
|
||||||
reply &&
|
// reply &&
|
||||||
typeof reply === "object" &&
|
// typeof reply === "object" &&
|
||||||
("firstPartyComment" in reply || "secondPartyComment" in reply);
|
// ("firstPartyComment" in reply || "secondPartyComment" in reply);
|
||||||
|
|
||||||
if (isSubmitReply(reply)) {
|
// if (isSubmitReply(reply)) {
|
||||||
const firstAccepted = reply.firstPartyComment?.isAccept;
|
// const firstAccepted = reply.firstPartyComment?.isAccept;
|
||||||
const secondAccepted = reply.secondPartyComment?.isAccept;
|
// const secondAccepted = reply.secondPartyComment?.isAccept;
|
||||||
|
|
||||||
if (!firstAccepted || !secondAccepted) {
|
// if (!firstAccepted || !secondAccepted) {
|
||||||
await this.requestDb.findByIdAndUpdate(request._id.toString(), {
|
// await this.requestDb.findByIdAndUpdate(request._id.toString(), {
|
||||||
blameStatus: ReqBlameStatus.CloseRequest,
|
// blameStatus: ReqBlameStatus.CloseRequest,
|
||||||
});
|
// });
|
||||||
this.logger.log(`Auto-closed request ${request._id}`);
|
// this.logger.log(`Auto-closed request ${request._id}`);
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
|
||||||
@Cron(CronExpression.EVERY_DAY_AT_2AM)
|
// @Cron(CronExpression.EVERY_DAY_AT_2AM)
|
||||||
async handleBlameFileCleanup() {
|
// async handleBlameFileCleanup() {
|
||||||
this.logger.log("Running scheduled blame file cleanup job...");
|
// this.logger.log("Running scheduled blame file cleanup job...");
|
||||||
|
|
||||||
const sevenDaysAgo = new Date();
|
// const sevenDaysAgo = new Date();
|
||||||
sevenDaysAgo.setDate(sevenDaysAgo.getDate() - 7);
|
// sevenDaysAgo.setDate(sevenDaysAgo.getDate() - 7);
|
||||||
|
|
||||||
const staleStatuses = [
|
// const staleStatuses = [
|
||||||
ReqBlameStatus.PendingForFirstParty,
|
// ReqBlameStatus.PendingForFirstParty,
|
||||||
ReqBlameStatus.PendingForSecondParty,
|
// ReqBlameStatus.PendingForSecondParty,
|
||||||
];
|
// ];
|
||||||
|
|
||||||
const query = {
|
// const query = {
|
||||||
createdAt: { $lt: sevenDaysAgo },
|
// createdAt: { $lt: sevenDaysAgo },
|
||||||
blameStatus: { $in: staleStatuses },
|
// blameStatus: { $in: staleStatuses },
|
||||||
};
|
// };
|
||||||
|
|
||||||
try {
|
// try {
|
||||||
const result = await this.requestDb.updateMany(query, {
|
// const result = await this.requestDb.updateMany(query, {
|
||||||
$set: { blameStatus: ReqBlameStatus.CloseRequest },
|
// $set: { blameStatus: ReqBlameStatus.CloseRequest },
|
||||||
});
|
// });
|
||||||
|
|
||||||
if (result.modifiedCount > 0) {
|
// if (result.modifiedCount > 0) {
|
||||||
this.logger.log(
|
// this.logger.log(
|
||||||
`Successfully closed ${result.modifiedCount} stale blame files.`,
|
// `Successfully closed ${result.modifiedCount} stale blame files.`,
|
||||||
);
|
// );
|
||||||
} else {
|
// } else {
|
||||||
this.logger.log("No stale blame files found to close.");
|
// this.logger.log("No stale blame files found to close.");
|
||||||
}
|
// }
|
||||||
} catch (error) {
|
// } catch (error) {
|
||||||
this.logger.error("Error during scheduled blame file cleanup:", error);
|
// this.logger.error("Error during scheduled blame file cleanup:", error);
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user