Initial commit after migration to gitea

This commit is contained in:
2026-01-18 11:27:43 +03:30
parent a21039410c
commit ea4b8eb543
196 changed files with 45567 additions and 9 deletions

View File

@@ -0,0 +1,18 @@
import { ApiProperty } from "@nestjs/swagger";
import { PlatesDto } from "src/plates/dto/plate.dto";
import { Plates } from "src/Types&Enums/plate.interface";
export class SandHubLoginDtoRs {
public loginToken: string;
constructor(token: string) {
this.loginToken = token;
}
}
export class SandHubDetailDto {
@ApiProperty({ type: String, required: true })
nationalCodeOfInsurer: string;
@ApiProperty({ type: PlatesDto, required: true })
plate: Plates;
}

View File

@@ -0,0 +1,35 @@
import { InjectModel } from "@nestjs/mongoose";
import { Model, Types, UpdateQuery } from "mongoose";
import { SandHubInterface, SandHubModel } from "../schema/sand-hub.schema";
export class SandHubDbService {
constructor(
@InjectModel(SandHubModel.name)
private readonly sandHubModel: Model<SandHubModel>,
) {}
async create(sandHubData: any): Promise<SandHubModel> {
return await this.sandHubModel.create(sandHubData);
}
async findOneByRequestId(requestId: string, userId): Promise<SandHubModel> {
return await this.sandHubModel.findOne({ requestId, userId });
}
async findOneBySandHubId(requestId: string): Promise<SandHubModel> {
return await this.sandHubModel.findOne({
_id: new Types.ObjectId(requestId),
});
}
async findOneByRequestIdAndUpdate(
requestId: string,
userId: string,
update: UpdateQuery<SandHubInterface>,
): Promise<any> {
return await this.sandHubModel.findOneAndUpdate(
{ requestId, userId },
update,
);
}
}

View File

@@ -0,0 +1,307 @@
import { Prop, Schema, SchemaFactory } from "@nestjs/mongoose";
import { Types } from "mongoose";
import { AddPlateDto } from "src/profile/dto/user/AddPlateDto";
export interface SandHubInterface {
requestId: string;
userId: string;
PrntPlcyCmpDocNo: string;
MapTypNam: string;
MtrNum: string;
ShsNum: string;
DisFnYrNum: string;
DisLfYrNum: string;
DisPrsnYrNum: string;
DisPrsnYrPrcnt: string;
DisFnYrPrcnt: string;
DisLfYrPrcnt: string;
vin: string;
MapVehicleSystemName: string;
LfCvrCptl: number;
FnCvrCptl: number;
PrsnCvrCptl: number;
VehicleSystemCode: number;
EdrsJson: string;
PersonCvrCptl: number;
LifeCvrCptl: number;
FinancialCvrCptl: number;
CarGroupCode: number;
CylCnt: number;
LastCompanyDocumentNumber: string;
UsageCode: number;
MapUsageCode: number;
MapUsageName: string;
Plk1: number;
Plk2: number;
Plk3: number;
PlkSrl: number;
PrintEndorsCompanyDocumentNumber: string;
EndorseDate: Date;
InsuranceFullName: string;
SystemField: string;
TypeField: string;
UsageField: string;
MainColorField: string;
SecondColorField: string;
ModelField: string;
CapacityField: string;
CylinderNumberField: string;
EngineNumberField: string;
ChassisNumberField: string;
VinNumberField: string;
InstallDateField: string;
AxelNumberField: string;
WheelNumberField: string;
CompanyName: string;
CompanyCode: string;
IssueDate: string;
SatrtDate: string;
EndDate: string;
Thrname: string;
EndorseText: string;
PolicyHealthLossCount: number;
PolicyFinancialLossCount: number;
PolicyPersonLossCount: number;
Tonage: number;
ThirdPolicyCode: number;
DiscountPersonPercent: number;
DiscountThirdPercent: number;
SystemCodeCii: number;
SystemNameCii: string;
TypeCodeCii: number;
TypeNameCii: string;
UsageNameCii: string;
UsageCodeCii: number;
ModelCii: number;
damageTypes: any[];
StatusTypeCode: number;
}
@Schema({ versionKey: false, collection: "sand-hub" })
export class SandHubModel {
@Prop()
requestId: string;
@Prop()
userId: string;
@Prop()
PlateId: Types.ObjectId;
@Prop()
PrntPlcyCmpDocNo: string;
@Prop()
MapTypNam: string;
@Prop()
MtrNum: string;
@Prop()
ShsNum: string;
@Prop()
DisFnYrNum: string;
@Prop()
DisLfYrNum: string;
@Prop()
DisPrsnYrNum: string;
@Prop()
DisPrsnYrPrcnt: string;
@Prop()
DisFnYrPrcnt: string;
@Prop()
DisLfYrPrcnt: string;
@Prop({ unique: false })
vin: string;
@Prop()
MapVehicleSystemName: string;
@Prop()
LfCvrCptl: number;
@Prop()
FnCvrCptl: number;
@Prop()
PrsnCvrCptl: number;
@Prop()
VehicleSystemCode: number;
@Prop()
EdrsJson: string;
@Prop()
PersonCvrCptl: number;
@Prop()
LifeCvrCptl: number;
@Prop()
FinancialCvrCptl: number;
@Prop()
CarGroupCode: number;
@Prop()
CylCnt: number;
@Prop()
LastCompanyDocumentNumber: string;
@Prop()
UsageCode: number;
@Prop()
MapUsageCode: number;
@Prop()
MapUsageName: string;
@Prop()
Plk1: number;
@Prop()
Plk2: number;
@Prop()
Plk3: number;
@Prop()
PlkSrl: number;
@Prop()
PrintEndorsCompanyDocumentNumber: string;
@Prop()
EndorseDate: Date;
@Prop()
InsuranceFullName: string;
@Prop()
SystemField: string;
@Prop()
TypeField: string;
@Prop()
UsageField: string;
@Prop()
MainColorField: string;
@Prop()
SecondColorField: string;
@Prop()
ModelField: string;
@Prop()
CapacityField: string;
@Prop()
CylinderNumberField: string;
@Prop()
EngineNumberField: string;
@Prop()
ChassisNumberField: string;
@Prop()
VinNumberField: string;
@Prop()
InstallDateField: string;
@Prop()
AxelNumberField: string;
@Prop()
WheelNumberField: string;
@Prop()
CompanyName: string;
@Prop()
CompanyCode: string;
@Prop()
IssueDate: string;
@Prop()
SatrtDate: string;
@Prop()
EndDate: string;
@Prop()
Thrname: string;
@Prop()
EndorseText: string;
@Prop()
PolicyHealthLossCount: number;
@Prop()
PolicyFinancialLossCount: number;
@Prop()
PolicyPersonLossCount: number;
@Prop()
Tonage: number;
@Prop()
ThirdPolicyCode: number;
@Prop()
DiscountPersonPercent: number;
@Prop()
DiscountThirdPercent: number;
@Prop()
SystemCodeCii: number;
@Prop()
SystemNameCii: string;
@Prop()
TypeCodeCii: number;
@Prop()
TypeNameCii: string;
@Prop()
UsageNameCii: string;
@Prop()
UsageCodeCii: number;
@Prop()
ModelCii: number;
@Prop()
damageTypes: any[];
@Prop()
StatusTypeCode: number;
@Prop({ type: Object })
plate: AddPlateDto["plate"];
@Prop()
nationalCode: string;
}
export const SandHubSchema = SchemaFactory.createForClass(SandHubModel);

View File

@@ -0,0 +1,19 @@
import { HttpModule } from "@nestjs/axios";
import { Module } from "@nestjs/common";
import { MongooseModule } from "@nestjs/mongoose";
import { SandHubDbService } from "src/sand-hub/entity/db-service/sand-hub.db.service";
import { SandHubModel, SandHubSchema } from "./entity/schema/sand-hub.schema";
import { SandHubService } from "./sand-hub.service";
@Module({
imports: [
HttpModule,
MongooseModule.forFeature([
{ name: SandHubModel.name, schema: SandHubSchema },
]),
],
providers: [SandHubService, SandHubDbService],
exports: [SandHubService, SandHubDbService],
controllers: [],
})
export class SandHubModule {}

View File

@@ -0,0 +1,380 @@
import { HttpService } from "@nestjs/axios";
import {
BadGatewayException,
BadRequestException,
GatewayTimeoutException,
Injectable,
Logger,
NotFoundException,
ServiceUnavailableException,
UnauthorizedException,
} from "@nestjs/common";
import axios from "axios";
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 { SandHubDetailDto } from "./dto/sand-hub.dto";
@Injectable()
export class SandHubService {
private readonly logger = new Logger(SandHubService.name);
private loginToken: string | null = null;
private tokenExpiry: Date | null = null;
constructor(
private readonly httpService: HttpService,
private readonly sandHubDbService: SandHubDbService,
) {}
private async getAccessToken(): Promise<string> {
if (this.loginToken && this.tokenExpiry && this.tokenExpiry > new Date()) {
return this.loginToken;
}
this.logger.log(
"SandHub token is missing or expired. Fetching a new one...",
);
try {
const response = await this.httpService.axiosRef.post(
process.env.SANDHUB_URL_LOGIN,
{
email: process.env.SANDHUB_USERNAME,
password: process.env.SANDHUB_PASSWORD,
},
);
const token = response.data.accessToken;
if (!token)
throw new Error("No access token returned from SandHub login");
this.loginToken = token;
this.tokenExpiry = new Date(Date.now() + 55 * 60 * 1000); // 55-minute expiry
this.logger.log("Successfully obtained a new SandHub token.");
return this.loginToken;
} catch (er) {
this.logger.error("Failed to login to SandHub:", er.message);
this.loginToken = null;
this.tokenExpiry = null;
throw new UnauthorizedException("SandHub authentication failed");
}
}
async getSandHubDataFromId(sandHubId) {
return await this.sandHubDbService.findOneBySandHubId(sandHubId);
}
private async makeSandHubRequest(url: string, payload: any, maxRetries = 3) {
const token = await this.getAccessToken();
const INITIAL_DELAY = 1000;
const BACKOFF_FACTOR = 2;
for (let attempt = 0; attempt < maxRetries; attempt++) {
try {
const response = await axios.post(url, payload, {
headers: {
Authorization: `Bearer ${token}`,
"Content-Type": "application/json",
Accept: "application/json",
},
timeout: 50000,
});
if (!response?.data) throw new Error("EMPTY_RESPONSE");
return response.data;
} catch (err) {
this.handleSandHubError(err, attempt, maxRetries);
const delay = INITIAL_DELAY * Math.pow(BACKOFF_FACTOR, attempt);
await new Promise((resolve) => setTimeout(resolve, delay));
}
}
throw new BadGatewayException(
"Failed to fetch data from SandHub after multiple retries",
);
}
/**
* Maps the new SandHub API response format to the old format expected by the application
*/
private mapNewApiResponseToOldFormat(newResponse: any): any {
if (!newResponse) return newResponse;
// Map the new field names to the old field names
return {
...newResponse,
// Company information
CompanyCode: newResponse.companyId || newResponse.CompanyCode,
CompanyName: newResponse.companyPersianName || newResponse.CompanyName,
// Vehicle information
MapTypNam: newResponse.vehiclePersianName || newResponse.MapTypNam,
UsageField: newResponse.persianCarType || newResponse.UsageField || newResponse.usgCod,
MapUsageName: newResponse.MapUsageName || newResponse.MapUsageName,
// Financial coverage
FinancialCvrCptl: newResponse.financeCoverage || newResponse.FinancialCvrCptl || "0",
// Dates
IssueDate: newResponse.persianStartDate || newResponse.IssueDate,
EndDate: newResponse.persianEndDate || newResponse.EndDate,
// Insurance details
LastCompanyDocumentNumber: newResponse.lastCompanyInsuranceNumber || newResponse.LastCompanyDocumentNumber || newResponse.insuranceNumber,
// Technical details
MtrNum: newResponse.MtrNum || newResponse.mtrnum,
ShsNum: newResponse.ShsNum || newResponse.shsNam || newResponse.ChassisNumberField,
vin: newResponse.vin || newResponse.VinNumberField || newResponse.vin,
VinNumberField: newResponse.vin || newResponse.VinNumberField,
ChassisNumberField: newResponse.ChassisNumberField || newResponse.shsNam,
EngineNumberField: newResponse.EngineNumberField || newResponse.mtrnum,
ModelField: newResponse.ModelField || newResponse.ModelField,
// Discount information
DisFnYrPrcnt: newResponse.DisFnYrPrcnt || newResponse.disFnYrPrcnt || "0",
DisLfYrPrcnt: newResponse.DisLfYrPrcnt || newResponse.disLfYrPrcnt || "0",
DisPrsnYrPrcnt: newResponse.DisPrsnYrPrcnt || newResponse.disPrsnYrPrcnt || "0",
DisFnYrNum: newResponse.DisFnYrNum || newResponse.disFnYrNum,
DisLfYrNum: newResponse.DisLfYrNum || newResponse.disLfYrNum,
DisPrsnYrNum: newResponse.DisPrsnYrNum || newResponse.disPrsnYrNum,
// Usage and vehicle system codes
UsageCode: newResponse.UsageCode || newResponse.usgCod,
VehicleSystemCode: newResponse.VehicleSystemCode || newResponse.vehSysCod,
CarGroupCode: newResponse.CarGroupCode || newResponse.carGrpCod,
// Policy information
ThirdPolicyCode: newResponse.ThirdPolicyCode || newResponse.ThirdPolicyCode,
// Endorsement data
EdrsJson: newResponse.EdrsJson || (newResponse.edrSes ? JSON.stringify(newResponse.edrSes) : undefined),
// Insurance fullname
InsuranceFullName: newResponse.InsuranceFullName || newResponse.fullname,
// National code
nationalCode: newResponse.nationalCode || newResponse.ntnlId,
};
}
async getSandHubResponse(userDetail: SandHubDetailDto) {
try {
const requestPayload = {
leftTwoDigits: String(userDetail.plate.leftDigits),
serialLetter: String(userDetail.plate.centerAlphabet),
threeDigits: String(userDetail.plate.centerDigits),
rightTwoDigits: String(userDetail.plate.ir),
nationalCode: userDetail.nationalCodeOfInsurer,
};
const requestUrl = `${process.env.SANDHUB_BASE_URL}/block-inquiry-tejarat`;
const response = await this.makeSandHubRequest(requestUrl, requestPayload);
// Map the new API response format to the old format
return this.mapNewApiResponseToOldFormat(response);
} catch (err) {
throw new Error(err);
}
}
async getPersonalInquiry(nationalCode: string, birthDate: number) {
try {
const requestUrl = `${process.env.SANDHUB_BASE_URL}/personal-inquiry/asia`;
const requestPayload = {
nin: nationalCode,
birthDate: birthDate,
persianBirthDate: "",
};
const response = await this.makeSandHubRequest(
requestUrl,
requestPayload,
);
if (response?.message?.includes("err.record.not.found")) {
throw new NotFoundException(
"Personal inquiry failed: Record not found for the given national code and birth date.",
);
}
return response.data;
} catch (err) {
throw new Error(`Error in finding personal inquiry: ${err}`);
}
}
async getDrivingLicenseInfo(
nationalCode: string,
driverLicenseNumber: string,
) {
const requestUrl = `${process.env.SANDHUB_BASE_URL}/driver-license-check`;
const requestPayload = {
driverLicenseNumber,
nationalCode,
};
this.logger.log(
`Fetching driving license info for national code: ${nationalCode}`,
);
try {
const response = await this.makeSandHubRequest(
requestUrl,
requestPayload,
);
if (response?.data?.IsSucceed === false) {
throw new NotFoundException(
"Driving license check failed: The license is not valid or could not be found.",
);
}
return response.data;
} catch (error) {
if (
error instanceof BadGatewayException &&
error.message.includes("multiple retries")
) {
throw new BadGatewayException(
`Driving license check failed after multiple retries. The service may be down.`,
);
}
// For all other errors (like 400, 404, etc.), re-throw them as-is.
throw new Error(`Error in finding driving license: ${error}`);
}
}
async getCarOwnershipInfo(plate: any, nationalCode: string) {
try {
const requestUrl = `${process.env.SANDHUB_BASE_URL}/ownership`;
const requestPayload = {
Plk1: String(plate.leftDigits),
Plk2: String(plate.centerAlphabet),
Plk3: String(plate.centerDigits),
plkSrl: String(plate.ir),
nationalCode: nationalCode,
};
this.logger.log(
`Checking car ownership for national code: ${nationalCode}`,
);
const response = await this.makeSandHubRequest(
requestUrl,
requestPayload,
);
// Check the 'IsSuccess' field in the nested 'data' object.
if (response?.data?.IsSuccess === false) {
this.logger.warn(
`Car ownership check failed for national code ${nationalCode}. Response:`,
response,
);
throw new BadRequestException(
"Ownership validation failed: The provided national ID is not the owner of this vehicle.",
);
}
return response;
} catch (err) {
throw new Error(`Error in finding car ownership: ${err}`);
}
}
async getShebaValidation(nationalId: string, shebaId: string) {
try {
const requestUrl = `${process.env.SANDHUB_BASE_URL}/sheba`;
const requestPayload = {
accountOwnerType: "1",
nationalId: nationalId,
legalId: "",
shebaId: shebaId,
};
this.logger.log(`Validating Sheba ID for national code: ${nationalId}`);
const response = await this.makeSandHubRequest(
requestUrl,
requestPayload,
);
if (response?.ReturnValue === false || response?.HasError === true) {
this.logger.warn(
`Sheba validation failed for national code ${nationalId}. Response:`,
response,
);
throw new BadRequestException(
"Sheba ID validation failed. The provided Sheba ID does not match the national ID.",
);
}
return response;
} catch (err) {
throw new Error(`Error in matching sheba validation: ${err}`);
}
}
private handleSandHubError(
err: any,
attempt: number,
maxRetries: number,
): void {
if (err.response) {
this.logger.error(
`❌ SandHub responded with status ${err.response.status} on attempt ${attempt + 1}:`,
JSON.stringify(err.response.data),
);
if (err.response.status === 400) {
throw new BadGatewayException(
`SandHub rejected the request with a 400 Bad Request. Details: ${JSON.stringify(err.response.data)}`,
);
}
} else {
this.logger.error(
`❌ Network error during SandHub request (attempt ${attempt + 1}):`,
err.message,
);
}
if (err.message === "EMPTY_RESPONSE") {
throw new BadGatewayException(
"SandHub is offline or returned an empty response",
);
}
if (err.code === "ECONNABORTED") {
throw new GatewayTimeoutException("SandHub request timed out");
}
if (err.code === "ECONNRESET" || err.message.includes("socket hang up")) {
throw new ServiceUnavailableException(
"SandHub connection was reset or closed unexpectedly",
);
}
// This final check is for when all retries have failed for a retryable error.
if (attempt >= maxRetries) {
throw new BadGatewayException(
"Failed to fetch data from SandHub after multiple retries",
err.message,
);
}
}
async sandHubDocumentCreator(
userId: string,
requestId: string,
doc: Partial<SandHubModel>,
) {
const existingDoc = await this.sandHubDbService.findOneByRequestId(
requestId,
userId,
);
if (!existingDoc) {
return await this.sandHubDbService.create({ ...doc, userId, requestId });
} else {
const updatePayload = { ...existingDoc, ...doc };
return await this.sandHubDbService.findOneByRequestIdAndUpdate(
requestId,
userId,
updatePayload,
);
}
}
}