forked from Yara724/api
Initial commit after migration to gitea
This commit is contained in:
26
.gitignore
vendored
26
.gitignore
vendored
@@ -1,4 +1,4 @@
|
||||
# ---> Node
|
||||
### Node ###
|
||||
# Logs
|
||||
logs
|
||||
*.log
|
||||
@@ -79,7 +79,7 @@ web_modules/
|
||||
.env.test.local
|
||||
.env.production.local
|
||||
.env.local
|
||||
|
||||
.env.development.env
|
||||
# parcel-bundler cache (https://parceljs.org/)
|
||||
.cache
|
||||
.parcel-cache
|
||||
@@ -103,13 +103,6 @@ dist
|
||||
|
||||
# vuepress v2.x temp and cache directory
|
||||
.temp
|
||||
.cache
|
||||
|
||||
# vitepress build output
|
||||
**/.vitepress/dist
|
||||
|
||||
# vitepress cache directory
|
||||
**/.vitepress/cache
|
||||
|
||||
# Docusaurus cache and generated files
|
||||
.docusaurus
|
||||
@@ -136,3 +129,18 @@ dist
|
||||
.yarn/install-state.gz
|
||||
.pnp.*
|
||||
|
||||
### Node Patch ###
|
||||
# Serverless Webpack directories
|
||||
.webpack/
|
||||
|
||||
# Optional stylelint cache
|
||||
|
||||
# SvelteKit build / generate output
|
||||
.svelte-kit
|
||||
|
||||
# End of https://www.toptal.com/developers/gitignore/api/node
|
||||
|
||||
/docker
|
||||
.development.env
|
||||
*.env
|
||||
/files
|
||||
|
||||
13
build_n_deploy.sh
Normal file
13
build_n_deploy.sh
Normal file
@@ -0,0 +1,13 @@
|
||||
git pull
|
||||
|
||||
docker compose -f dev.docker-compose.yml up -d --build
|
||||
|
||||
docker run --rm \
|
||||
-e SONAR_HOST_URL="https://sq.ittalie.ir" \
|
||||
-e SONAR_TOKEN="sqp_42fe574fde1e2d527813f4fb6912ad0040c6850a" \
|
||||
-v "$(pwd):/usr/src" \
|
||||
sonarsource/sonar-scanner-cli \
|
||||
-Dsonar.scm.provider=git \
|
||||
-Dsonar.projectKey=yara724-main-api \
|
||||
-Dsonar.sources=.
|
||||
|
||||
20
dev.Dockerfile
Normal file
20
dev.Dockerfile
Normal file
@@ -0,0 +1,20 @@
|
||||
# Use the official Node.js image as the base image
|
||||
FROM node:20-alpine
|
||||
|
||||
# Set the working directory inside the container
|
||||
WORKDIR /app
|
||||
|
||||
# Copy package.json and package-lock.json to the working directory
|
||||
COPY package*.json ./
|
||||
|
||||
# Install the project dependencies
|
||||
RUN npm i --force
|
||||
|
||||
# Copy the rest of the application code to the working directory
|
||||
COPY . .
|
||||
|
||||
# Expose the port on which your NestJS application will run
|
||||
# EXPOSE 9000
|
||||
|
||||
# Start the NestJS application
|
||||
CMD ["npm", "start"]
|
||||
35
dev.docker-compose.yml
Normal file
35
dev.docker-compose.yml
Normal file
@@ -0,0 +1,35 @@
|
||||
version: '3.7'
|
||||
|
||||
services:
|
||||
api:
|
||||
build:
|
||||
context: .
|
||||
dockerfile: dev.Dockerfile
|
||||
container_name: yara724-main-api-dev
|
||||
image: yara724-main-api:dev
|
||||
restart: unless-stopped
|
||||
volumes:
|
||||
- yara724-main-api-dev-files:/app/files/:rw
|
||||
env_file:
|
||||
- .development.env
|
||||
#environment:
|
||||
# - "MONGODB_USERNAME=ittalie_dev"
|
||||
# - "MONGODB_PASSWORD=t3hYwwj9jbu8QB6aMxt8KLnj3qEQDuMM"
|
||||
# - "MONGODB_HOSTNAME=db.ittalie.ir"
|
||||
# - "MONGODB_PORT=3082"
|
||||
# - "NODE_ENV=development"
|
||||
# - "PORT=3002"
|
||||
# - "SWAGGER_USER=admin"
|
||||
# - "SWAGGER_PASSWORD=123321"
|
||||
# - "SECRET=h43h$24qaw849f3912d@3as"
|
||||
ports:
|
||||
- 3080:3002
|
||||
networks:
|
||||
default:
|
||||
driver: bridge
|
||||
ipam:
|
||||
config:
|
||||
- subnet: 172.16.58.0/24
|
||||
volumes:
|
||||
yara724-main-api-dev-files:
|
||||
name: yara724-main-api-dev-files
|
||||
1
err.json
Normal file
1
err.json
Normal file
@@ -0,0 +1 @@
|
||||
{"1000":{"info":"start","message":"start"},"1001":{"info":"Access Denied Other Actor Lock File","message":""},"1004":{"info":"g","message":""},"1005":{"info":"fSF","message":""},"1006":{"info":"request not found ","message":""}}
|
||||
8
nest-cli.json
Normal file
8
nest-cli.json
Normal file
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/nest-cli",
|
||||
"collection": "@nestjs/schematics",
|
||||
"sourceRoot": "src",
|
||||
"compilerOptions": {
|
||||
"deleteOutDir": true
|
||||
}
|
||||
}
|
||||
19051
package-lock.json
generated
Normal file
19051
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
109
package.json
Normal file
109
package.json
Normal file
@@ -0,0 +1,109 @@
|
||||
{
|
||||
"name": "yara724",
|
||||
"version": "0.0.1",
|
||||
"description": "",
|
||||
"author": "",
|
||||
"private": true,
|
||||
"license": "UNLICENSED",
|
||||
"scripts": {
|
||||
"build": "nest build",
|
||||
"format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\"",
|
||||
"start": "nest start",
|
||||
"start:dev": "nest start --watch",
|
||||
"start:debug": "nest start --debug --watch",
|
||||
"start:prod": "node dist/main",
|
||||
"lint": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix",
|
||||
"test": "jest",
|
||||
"test:watch": "jest --watch",
|
||||
"test:cov": "jest --coverage",
|
||||
"test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand",
|
||||
"test:e2e": "jest --config ./test/jest-e2e.json"
|
||||
},
|
||||
"dependencies": {
|
||||
"@arashioz/errjson-talieh": "^2.2.5",
|
||||
"@fraybabak/kavenegar_nest": "^1.0.5",
|
||||
"@nestjs-modules/mailer": "^2.0.2",
|
||||
"@nestjs/axios": "^3.1.3",
|
||||
"@nestjs/common": "^10.4.15",
|
||||
"@nestjs/core": "^10.4.15",
|
||||
"@nestjs/jwt": "^10.2.0",
|
||||
"@nestjs/mapped-types": "*",
|
||||
"@nestjs/mongoose": "^10.1.0",
|
||||
"@nestjs/passport": "^10.0.3",
|
||||
"@nestjs/platform-express": "^10.4.15",
|
||||
"@nestjs/platform-fastify": "^10.4.15",
|
||||
"@nestjs/platform-socket.io": "^10.4.15",
|
||||
"@nestjs/schedule": "^4.1.2",
|
||||
"@nestjs/serve-static": "^5.0.3",
|
||||
"@nestjs/swagger": "^8.1.0",
|
||||
"@nestjs/websockets": "^10.4.15",
|
||||
"@types/uuid": "^10.0.0",
|
||||
"axios": "^1.9.0",
|
||||
"bcrypt": "^5.1.1",
|
||||
"class-transformer": "^0.5.1",
|
||||
"class-validator": "^0.14.1",
|
||||
"crypto": "^1.0.1",
|
||||
"dotenv": "^16.4.7",
|
||||
"express-basic-auth": "^1.2.1",
|
||||
"fastest-levenshtein": "^1.0.16",
|
||||
"form-data": "^4.0.2",
|
||||
"jalali-moment": "^3.3.11",
|
||||
"kavenegar": "^1.1.4",
|
||||
"mongoose": "^8.9.2",
|
||||
"nestjs-command": "^3.1.4",
|
||||
"passport": "^0.7.0",
|
||||
"passport-jwt": "^4.0.1",
|
||||
"passport-local": "^1.0.0",
|
||||
"reflect-metadata": "^0.2.2",
|
||||
"rxjs": "^7.8.1",
|
||||
"short-unique-id": "^5.2.0",
|
||||
"standard": "^17.1.2",
|
||||
"standardjs": "^1.0.0-alpha",
|
||||
"uuid": "^11.0.3",
|
||||
"yargs": "^17.7.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@nestjs/cli": "^10.4.9",
|
||||
"@nestjs/schematics": "^10.2.3",
|
||||
"@nestjs/testing": "^10.4.15",
|
||||
"@types/express": "^5.0.0",
|
||||
"@types/jest": "^29.5.14",
|
||||
"@types/multer": "^1.4.12",
|
||||
"@types/node": "^22.10.2",
|
||||
"@types/passport-jwt": "^4.0.1",
|
||||
"@types/supertest": "^6.0.2",
|
||||
"@types/yargs": "^17.0.33",
|
||||
"@typescript-eslint/eslint-plugin": "^8.18.1",
|
||||
"@typescript-eslint/parser": "^8.18.1",
|
||||
"eslint": "^9.17.0",
|
||||
"eslint-config-prettier": "^9.1.0",
|
||||
"eslint-plugin-prettier": "^5.2.1",
|
||||
"jest": "^29.7.0",
|
||||
"prettier": "^3.4.2",
|
||||
"source-map-support": "^0.5.21",
|
||||
"supertest": "^7.0.0",
|
||||
"ts-jest": "^29.2.5",
|
||||
"ts-loader": "^9.5.1",
|
||||
"ts-node": "^10.9.2",
|
||||
"ts-standard": "^12.0.2",
|
||||
"tsconfig-paths": "^4.2.0",
|
||||
"typescript": "^5.7.2"
|
||||
},
|
||||
"jest": {
|
||||
"moduleFileExtensions": [
|
||||
"js",
|
||||
"json",
|
||||
"ts"
|
||||
],
|
||||
"rootDir": "src",
|
||||
"testRegex": ".*\\.spec\\.ts$",
|
||||
"transform": {
|
||||
"^.+\\.(t|j)s$": "ts-jest"
|
||||
},
|
||||
"collectCoverageFrom": [
|
||||
"**/*.(t|j)s"
|
||||
],
|
||||
"coverageDirectory": "../coverage",
|
||||
"testEnvironment": "node"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
// Weather conditions for accident
|
||||
export enum WeatherCondition {
|
||||
CLEAR = "صاف",
|
||||
RAINY = "بارونی",
|
||||
SNOWY = "برفی",
|
||||
FOGGY = "مه آلود",
|
||||
}
|
||||
|
||||
// Road conditions for accident
|
||||
export enum RoadCondition {
|
||||
MUDDY = "گلی",
|
||||
ICY = "یخ زده",
|
||||
WET = "مرطوب",
|
||||
DRY = "خشک",
|
||||
}
|
||||
|
||||
// Light conditions for accident
|
||||
export enum LightCondition {
|
||||
DAYLIGHT = "روز",
|
||||
NIGHT = "شب",
|
||||
LOW_LIGHT = "کم نور",
|
||||
}
|
||||
|
||||
14
src/Types&Enums/blame-request-management/status.enum.ts
Normal file
14
src/Types&Enums/blame-request-management/status.enum.ts
Normal file
@@ -0,0 +1,14 @@
|
||||
export enum ReqBlameStatus {
|
||||
PendingForSecondParty = "PendingForSecondParty",
|
||||
PendingForFirstParty = "PendingForFirstParty",
|
||||
UnChecked = "UnChecked",
|
||||
CheckedRequest = "CheckedRequest",
|
||||
ReviewRequest = "ReviewRequest",
|
||||
CheckAgain = "CheckAgain",
|
||||
UserPending = "UserPending",
|
||||
CloseRequest = "CloseRequest",
|
||||
WaitForUserAccept = "WaitForUserAccept",
|
||||
WaitingForSignatures = "WaitingForSignatures",
|
||||
PartiesDisagree = "PartiesDisagree",
|
||||
InPersonVisit = "InPersonVisit",
|
||||
}
|
||||
26
src/Types&Enums/blame-request-management/steps.enum.ts
Normal file
26
src/Types&Enums/blame-request-management/steps.enum.ts
Normal file
@@ -0,0 +1,26 @@
|
||||
export enum StepsEnum {
|
||||
createRequest = "createRequest",
|
||||
F_InitialForm = "firstParty-initialForm",
|
||||
F_addPlate = "firstParty-addPlate",
|
||||
F_videoUpload = "firstParty-videoUpload",
|
||||
F_addLocation = "firstParty-addLocation",
|
||||
F_addVoice = "firstParty-addVoice",
|
||||
F_addDescription = "firstParty-addDescription",
|
||||
F_addSecondPartyPhoneNumber = "firstParty-addSecondPartyPhoneNumber",
|
||||
F_addSign = "firstParty-addSign",
|
||||
F_completed = "firstParty-completed",
|
||||
S_InitialForm = "secondParty-initialForm",
|
||||
S_addPlate = "secondParty-addPlate",
|
||||
S_addLocation = "secondParty-addLocation",
|
||||
S_addVoice = "secondParty-addVoice",
|
||||
S_addDescription = "secondParty-addDescription",
|
||||
S_addSecondPartyPhoneNumber = "S_addSecondPartyPhoneNumber",
|
||||
S_addSign = "secondParty-addSign",
|
||||
S_completed = "secondParty-completed",
|
||||
AddSignatures = "AddSignatures",
|
||||
CarBodyVideo = "CarBodyVideo",
|
||||
CarBodyForm = "CarBodyForm",
|
||||
CarBodySecondForm = "CarBodySecondForm",
|
||||
CarBodyDamaged = "CarBodyDamaged",
|
||||
CarBodyGuilty = "CarBodyGuilty",
|
||||
}
|
||||
21
src/Types&Enums/claim-request-management/car-part.enum.ts
Normal file
21
src/Types&Enums/claim-request-management/car-part.enum.ts
Normal file
@@ -0,0 +1,21 @@
|
||||
export enum CarPartEnum {
|
||||
LeftBackFender = "leftBackFender",
|
||||
RightBackFender = "rightBackFender",
|
||||
BackWheel = "backWheel",
|
||||
LeftBackDoor = "leftBackDoor",
|
||||
RightBackDoor = "rightBackDoor",
|
||||
LeftFrontDoor = "leftFrontDoor",
|
||||
RightFrontDoor = "rightFrontDoor",
|
||||
LeftMirror = "leftMirror",
|
||||
RightMirror = "rightMirror",
|
||||
FrontWheel = "frontWheel",
|
||||
LeftFrontFender = "leftFrontFender",
|
||||
RightFrontFender = "RightFrontFender",
|
||||
FrontBumper = "frontBumper",
|
||||
FrontCarWindow = "frontCarWindow",
|
||||
CarHood = "carHood",
|
||||
BackBumper = "backBumper",
|
||||
CarTrunk = "carTrunk",
|
||||
BackCarWindow = "backCarWindow",
|
||||
Roof = "roof",
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
export enum DaghiOption {
|
||||
RECYCLED_PARTS_VALUE = "ارزش لوازم بازیافتی",
|
||||
DELIVER_DAMAGED_PART = "تحویل داغی",
|
||||
NO_VALUE = "فاقد ارزش",
|
||||
WITH_DAMAGED_PART_CALCULATION = "با احتساب داغی",
|
||||
}
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
export class PriceDropIF {
|
||||
total: number;
|
||||
carPrice: number;
|
||||
carModel: number;
|
||||
carValue: number[];
|
||||
sumOfSeverity: number;
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
export enum FactorStatus {
|
||||
PENDING = "PENDING",
|
||||
APPROVED = "APPROVED",
|
||||
REJECTED = "REJECTED",
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
export enum InPersonDocumentsEnum {
|
||||
NationalCertificate = "nationalCertificate",
|
||||
CarCertificate = "carCertificate",
|
||||
DrivingLicense = "drivingLicense",
|
||||
CarGreenCard = "carGreenCard",
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
export enum ClaimRequiredDocumentType {
|
||||
// Damaged party documents
|
||||
DAMAGED_DRIVING_LICENSE_BACK = "damaged_driving_license_back",
|
||||
DAMAGED_DRIVING_LICENSE_FRONT = "damaged_driving_license_front",
|
||||
DAMAGED_CHASSIS_NUMBER = "damaged_chassis_number",
|
||||
DAMAGED_ENGINE_PHOTO = "damaged_engine_photo",
|
||||
DAMAGED_CAR_CARD_FRONT = "damaged_car_card_front",
|
||||
DAMAGED_CAR_CARD_BACK = "damaged_car_card_back",
|
||||
DAMAGED_METAL_PLATE = "damaged_metal_plate",
|
||||
|
||||
// Guilty party documents
|
||||
GUILTY_DRIVING_LICENSE_FRONT = "guilty_driving_license_front",
|
||||
GUILTY_DRIVING_LICENSE_BACK = "guilty_driving_license_back",
|
||||
GUILTY_CAR_CARD_FRONT = "guilty_car_card_front",
|
||||
GUILTY_CAR_CARD_BACK = "guilty_car_card_back",
|
||||
GUILTY_METAL_PLATE = "guilty_metal_plate",
|
||||
}
|
||||
|
||||
15
src/Types&Enums/claim-request-management/status.enum.ts
Normal file
15
src/Types&Enums/claim-request-management/status.enum.ts
Normal file
@@ -0,0 +1,15 @@
|
||||
export enum ReqClaimStatus {
|
||||
WaitingForUserCompleted = "WaitingForUserCompleted",
|
||||
UnChecked = "UnChecked",
|
||||
CheckedRequest = "CheckedRequest",
|
||||
ReviewRequest = "ReviewRequest",
|
||||
CheckAgain = "CheckAgain",
|
||||
UserPending = "UserPending",
|
||||
CloseRequest = "CloseRequest",
|
||||
WaitingForUserToResend = "WaitingForUserToResend",
|
||||
InPersonVisit = "InPersonVisit",
|
||||
PendingFactorUpload = "PendingFactorUpload",
|
||||
PendingFactorValidation = "PendingFactorValidation",
|
||||
FactorRejected = "FactorRejected",
|
||||
UploadingRequiredDocuments = "UploadingRequiredDocuments",
|
||||
}
|
||||
10
src/Types&Enums/claim-request-management/steps.enum.ts
Normal file
10
src/Types&Enums/claim-request-management/steps.enum.ts
Normal file
@@ -0,0 +1,10 @@
|
||||
export enum ClaimStepsEnum {
|
||||
CreateClaimFile = "createClaimFile",
|
||||
SelectDamagePart = "selectDamagePart",
|
||||
SelectOtherParts = "SelectOtherParts",
|
||||
UploadRequiredDocuments = "uploadRequiredDocuments",
|
||||
ImageRequired = "ImageRequired",
|
||||
waitForDamageExpertComment = "waitForDamageExpertComment",
|
||||
WaitingForUserToReact = "WaitingForUserToReact",
|
||||
WaitingForFactorUpload = "WaitingForFactorUpload",
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
export enum TypeOfDamage {
|
||||
Repair = "repair",
|
||||
Change = "change",
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
export enum UserReplyEnum {
|
||||
HOLD = "HOLD",
|
||||
ACCEPTED = "ACCEPTED",
|
||||
REJECTED = "REJECTED",
|
||||
}
|
||||
32
src/Types&Enums/damage-expert.enum.ts
Normal file
32
src/Types&Enums/damage-expert.enum.ts
Normal file
@@ -0,0 +1,32 @@
|
||||
export enum ExpertizedAtEnum {
|
||||
BADANE = "badane",
|
||||
SAALES = "saales",
|
||||
FANI = "fani",
|
||||
SPECIALIZED = "specialized",
|
||||
}
|
||||
|
||||
export enum PreviousWorkEnum {
|
||||
INSURANCE_COMPANY = "insuranceCompany",
|
||||
BROKER = "broker",
|
||||
GENUINE_EXPERT = "genuineExpert",
|
||||
}
|
||||
|
||||
export enum SkillEnum {
|
||||
SMOOTHING = "smothing",
|
||||
COLORING = "coloring",
|
||||
PARTS_AUTH = "partsAuth",
|
||||
MEDIA_EVAL = "mediaEval",
|
||||
SCENE_EXPERT = "sceneExpert",
|
||||
DYNAMIC_ANALYSIS = "dynamicAnalysis",
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
6
src/Types&Enums/degrees.enum.ts
Normal file
6
src/Types&Enums/degrees.enum.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
export enum Degrees {
|
||||
DIPLOMA = "diploma",
|
||||
EXPERT = "expert",
|
||||
SENIOR_EXPERT = "senior_expert",
|
||||
PHD = "phd",
|
||||
}
|
||||
8
src/Types&Enums/plate.interface.ts
Normal file
8
src/Types&Enums/plate.interface.ts
Normal file
@@ -0,0 +1,8 @@
|
||||
export interface Plates {
|
||||
leftDigits: number;
|
||||
centerAlphabet: string;
|
||||
centerDigits: number;
|
||||
ir: number;
|
||||
nationalCode: string;
|
||||
carDetail?: any;
|
||||
}
|
||||
7
src/Types&Enums/role.enum.ts
Normal file
7
src/Types&Enums/role.enum.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
export enum RoleEnum {
|
||||
EXPERT = "expert",
|
||||
DAMAGE_EXPERT = "damage_expert",
|
||||
COMPANY = "company",
|
||||
ADMIN = "admin",
|
||||
USER = "user",
|
||||
}
|
||||
4
src/Types&Enums/upload.enum.ts
Normal file
4
src/Types&Enums/upload.enum.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
export enum UploaderModeEnum {
|
||||
Sign = "uploadService",
|
||||
Certificate = "certService",
|
||||
}
|
||||
5
src/Types&Enums/userType.enum.ts
Normal file
5
src/Types&Enums/userType.enum.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
export enum UserType {
|
||||
GENUINE = "genuine",
|
||||
LEGAL = "legal",
|
||||
INSURER = "insurer",
|
||||
}
|
||||
10
src/ai/ai.module.ts
Normal file
10
src/ai/ai.module.ts
Normal file
@@ -0,0 +1,10 @@
|
||||
import { HttpModule } from "@nestjs/axios";
|
||||
import { Module } from "@nestjs/common";
|
||||
import { AiService } from "./ai.service";
|
||||
|
||||
@Module({
|
||||
imports: [HttpModule],
|
||||
providers: [AiService],
|
||||
exports: [AiService],
|
||||
})
|
||||
export class AiModule {}
|
||||
273
src/ai/ai.service.ts
Normal file
273
src/ai/ai.service.ts
Normal file
@@ -0,0 +1,273 @@
|
||||
import { createReadStream, existsSync } from "node:fs";
|
||||
import { join } from "node:path";
|
||||
import {
|
||||
HttpException,
|
||||
HttpStatus,
|
||||
Injectable,
|
||||
Logger,
|
||||
OnModuleInit,
|
||||
} from "@nestjs/common";
|
||||
import axios, { AxiosRequestConfig } from "axios";
|
||||
import * as FormData from "form-data";
|
||||
|
||||
@Injectable()
|
||||
export class AiService implements OnModuleInit {
|
||||
private readonly logger = new Logger(AiService.name);
|
||||
private apiKey: string;
|
||||
private accessToken: string = null;
|
||||
|
||||
// These configurations are for authentication and getting the API key.
|
||||
private readonly loginOptions: AxiosRequestConfig = {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
url: `${process.env.AI_URL_V2}/auth/login`,
|
||||
data: {
|
||||
username: process.env.AI_USERNAME,
|
||||
password: process.env.AI_PASSWORD,
|
||||
},
|
||||
timeout: 30000, // 30 second timeout
|
||||
};
|
||||
|
||||
private get profileOptions(): AxiosRequestConfig {
|
||||
return {
|
||||
method: "GET",
|
||||
url: `${process.env.AI_URL_V2}/auth/profile`,
|
||||
headers: {
|
||||
Authorization: `Bearer ${this.accessToken}`,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
// This getter dynamically creates the base options for the image processing request.
|
||||
private get imageProcessOptions(): AxiosRequestConfig {
|
||||
return {
|
||||
method: "POST",
|
||||
url: `${process.env.AI_URL_V2}/services/car-damage/detector?version=ai-v7`,
|
||||
headers: {
|
||||
Authorization: `Bearer ${this.accessToken}`,
|
||||
"gateway-api-key": `${this.apiKey}`,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
constructor() {}
|
||||
|
||||
async onModuleInit() {
|
||||
try {
|
||||
const res = await this.login();
|
||||
if (res?.accessToken) {
|
||||
this.logger.verbose("AI Service Authenticated Successfully.");
|
||||
this.accessToken = res.accessToken;
|
||||
await this.getApiKey();
|
||||
this.logger.log("AI Service initialized and ready.");
|
||||
} else {
|
||||
this.logger.warn(
|
||||
"AI Service Unavailable: Login did not return an access token. Will retry on first request.",
|
||||
);
|
||||
}
|
||||
} catch (error) {
|
||||
// Don't prevent app startup if AI service is temporarily unavailable
|
||||
// The service will attempt to re-authenticate when aiRequestImage is called
|
||||
this.logger.warn(
|
||||
"AI Service Unavailable: Failed during initial login. Will retry on first request.",
|
||||
);
|
||||
this.logger.warn(`Error: ${error.message}`);
|
||||
// Reset tokens so re-authentication will be attempted
|
||||
this.accessToken = null;
|
||||
this.apiKey = null;
|
||||
}
|
||||
}
|
||||
|
||||
private async login() {
|
||||
try {
|
||||
const loginResponse = await axios.request(this.loginOptions);
|
||||
return loginResponse.data;
|
||||
} catch (err) {
|
||||
const errorMessage = err.response?.data?.message || err.message || "Unknown error";
|
||||
const statusCode = err.response?.status || 500;
|
||||
this.logger.error(`AI login failed: ${errorMessage} (Status: ${statusCode})`);
|
||||
if (err.response?.data) {
|
||||
this.logger.error(`AI login error details: ${JSON.stringify(err.response.data, null, 2)}`);
|
||||
}
|
||||
throw new HttpException(
|
||||
`Could not authenticate with AI service: ${errorMessage}`,
|
||||
statusCode >= 400 && statusCode < 500 ? statusCode : HttpStatus.UNAUTHORIZED,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private async getApiKey() {
|
||||
try {
|
||||
const profileResponse = await axios.request(this.profileOptions);
|
||||
this.apiKey = profileResponse.data.apiKey.key;
|
||||
this.logger.log("Successfully retrieved AI gateway API key.");
|
||||
return this.apiKey;
|
||||
} catch (err) {
|
||||
this.logger.error("Failed to retrieve AI API key:", err.message);
|
||||
throw new HttpException(
|
||||
"Could not get API key from AI service",
|
||||
HttpStatus.FAILED_DEPENDENCY,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
public async aiRequestImage(file: { path: string; fileName?: string }): Promise<any> {
|
||||
// Ensure authentication is set up
|
||||
if (!this.accessToken || !this.apiKey) {
|
||||
this.logger.warn("AI service not authenticated, attempting to re-authenticate...");
|
||||
try {
|
||||
const res = await this.login();
|
||||
if (res?.accessToken) {
|
||||
this.accessToken = res.accessToken;
|
||||
await this.getApiKey();
|
||||
} else {
|
||||
throw new HttpException(
|
||||
"AI Service authentication failed",
|
||||
HttpStatus.UNAUTHORIZED,
|
||||
);
|
||||
}
|
||||
} catch (error) {
|
||||
this.logger.error("Failed to re-authenticate AI service:", error.message);
|
||||
throw new HttpException(
|
||||
"AI Service authentication failed",
|
||||
HttpStatus.UNAUTHORIZED,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Resolve relative paths to absolute paths
|
||||
const filePath = file.path.startsWith("/")
|
||||
? file.path
|
||||
: join(process.cwd(), file.path.replace(/^\.\//, ""));
|
||||
|
||||
this.logger.log(`Processing AI image request for: ${filePath}`);
|
||||
|
||||
// Check if file exists
|
||||
if (!existsSync(filePath)) {
|
||||
this.logger.error(`File not found at path: ${filePath}`);
|
||||
throw new HttpException(
|
||||
`File not found: ${file.path}`,
|
||||
HttpStatus.NOT_FOUND,
|
||||
);
|
||||
}
|
||||
|
||||
const form = new FormData();
|
||||
const fileStream = createReadStream(filePath);
|
||||
|
||||
// Append file with filename if available
|
||||
if (file.fileName) {
|
||||
form.append("images", fileStream, file.fileName);
|
||||
} else {
|
||||
// Extract filename from path if not provided
|
||||
const pathParts = filePath.split("/");
|
||||
const extractedFileName = pathParts[pathParts.length - 1];
|
||||
form.append("images", fileStream, extractedFileName);
|
||||
}
|
||||
|
||||
try {
|
||||
const requestHeaders = {
|
||||
...this.imageProcessOptions.headers,
|
||||
...form.getHeaders(),
|
||||
};
|
||||
|
||||
this.logger.log(`[STEP 1/4] Sending request to AI service: ${this.imageProcessOptions.url}`);
|
||||
this.logger.log(`[STEP 1/4] File: ${filePath}, Filename: ${file.fileName || 'extracted from path'}`);
|
||||
this.logger.log(`[STEP 1/4] FormData Content-Type: ${form.getHeaders()['content-type']}`);
|
||||
this.logger.log(`[STEP 1/4] Authorization header present: ${!!requestHeaders.Authorization}`);
|
||||
this.logger.log(`[STEP 1/4] Gateway API key present: ${!!this.apiKey}`);
|
||||
this.logger.log(`[STEP 1/4] Request method: POST`);
|
||||
this.logger.log(`[STEP 1/4] FormData field name: "images"`);
|
||||
|
||||
// Get file stats for debugging
|
||||
const fs = require('fs');
|
||||
const stats = fs.statSync(filePath);
|
||||
this.logger.log(`[STEP 1/4] File size: ${stats.size} bytes`);
|
||||
this.logger.log(`[STEP 1/4] File exists: ${existsSync(filePath)}`);
|
||||
|
||||
const response = await axios.request({
|
||||
...this.imageProcessOptions,
|
||||
headers: requestHeaders,
|
||||
data: form,
|
||||
maxContentLength: Infinity,
|
||||
maxBodyLength: Infinity,
|
||||
});
|
||||
|
||||
this.logger.log(`[STEP 2/4] Successfully received response from AI service (Status: ${response.status})`);
|
||||
|
||||
// Validate response structure
|
||||
if (!response.data) {
|
||||
this.logger.error(`[ERROR] AI response is empty or missing data`);
|
||||
throw new HttpException(
|
||||
"AI Service returned empty response",
|
||||
HttpStatus.BAD_GATEWAY,
|
||||
);
|
||||
}
|
||||
|
||||
// Check for error in response first (AI service returns 201 with error in body)
|
||||
if (response.data.error) {
|
||||
this.logger.error(`[ERROR] AI service returned an error in response body`);
|
||||
this.logger.error(`[ERROR] Error message: ${response.data.error}`);
|
||||
this.logger.error(`[ERROR] Full response: ${JSON.stringify(response.data, null, 2)}`);
|
||||
throw new HttpException(
|
||||
`AI Service error: ${response.data.error}`,
|
||||
HttpStatus.BAD_GATEWAY,
|
||||
);
|
||||
}
|
||||
|
||||
// Check for processed image (downloadLink)
|
||||
if (!response.data.downloadLink) {
|
||||
this.logger.error(`[ERROR] AI response missing processed image (downloadLink)`);
|
||||
this.logger.error(`[ERROR] Response structure: ${JSON.stringify(Object.keys(response.data))}`);
|
||||
this.logger.error(`[ERROR] Full response: ${JSON.stringify(response.data, null, 2)}`);
|
||||
throw new HttpException(
|
||||
"AI Service did not return processed image (downloadLink missing)",
|
||||
HttpStatus.BAD_GATEWAY,
|
||||
);
|
||||
}
|
||||
|
||||
// Check for reports
|
||||
if (!response.data.reports) {
|
||||
this.logger.warn(`[WARNING] AI response missing reports object, but downloadLink exists`);
|
||||
this.logger.warn(`[WARNING] Response keys: ${JSON.stringify(Object.keys(response.data))}`);
|
||||
}
|
||||
|
||||
this.logger.log(`[STEP 3/4] Validated AI response - downloadLink: ${response.data.downloadLink ? 'present' : 'missing'}, reports: ${response.data.reports ? 'present' : 'missing'}`);
|
||||
|
||||
return response.data;
|
||||
} catch (er) {
|
||||
// Determine error source
|
||||
let errorSource = "UNKNOWN";
|
||||
let errorMessage = er.message;
|
||||
let errorDetails = "No error details available";
|
||||
|
||||
if (er.response) {
|
||||
errorSource = "AI_SERVICE_RESPONSE";
|
||||
errorMessage = er.response?.data?.message || er.message || `HTTP ${er.response.status}`;
|
||||
errorDetails = er.response?.data
|
||||
? JSON.stringify(er.response.data, null, 2)
|
||||
: `Status: ${er.response.status}, StatusText: ${er.response.statusText}`;
|
||||
} else if (er.request) {
|
||||
errorSource = "NETWORK_ERROR";
|
||||
errorMessage = "Network error - AI service did not respond";
|
||||
errorDetails = "Request was made but no response received";
|
||||
} else {
|
||||
errorSource = "REQUEST_SETUP_ERROR";
|
||||
errorMessage = er.message || "Error setting up request";
|
||||
}
|
||||
|
||||
this.logger.error(`[ERROR] AI request failed - Source: ${errorSource}`);
|
||||
this.logger.error(`[ERROR] File path: ${filePath}`);
|
||||
this.logger.error(`[ERROR] Error message: ${errorMessage}`);
|
||||
this.logger.error(`[ERROR] Error details: ${errorDetails}`);
|
||||
if (er.stack) {
|
||||
this.logger.error(`[ERROR] Stack trace: ${er.stack}`);
|
||||
}
|
||||
|
||||
// Re-throw with detailed error information
|
||||
throw new HttpException(
|
||||
`[${errorSource}] ${errorMessage}`,
|
||||
er.response?.status || HttpStatus.INTERNAL_SERVER_ERROR,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
66
src/app.module.ts
Normal file
66
src/app.module.ts
Normal file
@@ -0,0 +1,66 @@
|
||||
import { join } from "node:path";
|
||||
import { Module } from "@nestjs/common";
|
||||
import { MongooseModule } from "@nestjs/mongoose";
|
||||
import { ScheduleModule } from "@nestjs/schedule";
|
||||
import { ServeStaticModule } from "@nestjs/serve-static";
|
||||
import * as dotenv from "dotenv";
|
||||
import { CommandModule } from "nestjs-command";
|
||||
import { AiModule } from "./ai/ai.module";
|
||||
import { AuthModule } from "./auth/auth.module";
|
||||
import { ClaimRequestManagementModule } from "./claim-request-management/claim-request-management.module";
|
||||
import { ClientModule } from "./client/client.module";
|
||||
import { ExpertBlameModule } from "./expert-blame/expert-blame.module";
|
||||
import { ExpertClaimModule } from "./expert-claim/expert-claim.module";
|
||||
import { ExpertInsurerModule } from "./expert-insurer/expert-insurer.module";
|
||||
import { LookupsModule } from "./lookups/lookups.module";
|
||||
import { PlatesModule } from "./plates/plates.module";
|
||||
import { ProfileModule } from "./profile/profile.module";
|
||||
import { SandHubModule } from "./sand-hub/sand-hub.module";
|
||||
import { ReportsModule } from "./reports/reports.module";
|
||||
import { RequestManagementModule } from "./request-management/request-management.module";
|
||||
import { UsersModule } from "./users/users.module";
|
||||
import { CronModule } from "./utils/cron/cron.module";
|
||||
|
||||
dotenv.config();
|
||||
dotenv.config({ path: `.${process.env.NODE_ENV}.env` });
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
CommandModule,
|
||||
ScheduleModule.forRoot(),
|
||||
CronModule,
|
||||
ServeStaticModule.forRoot({
|
||||
rootPath: join(__dirname, "..", "files"),
|
||||
serveRoot: "/files",
|
||||
}),
|
||||
MongooseModule.forRoot(
|
||||
`mongodb://${process.env.MONGO_URL}:${process.env.MONGO_PORT}/`,
|
||||
{
|
||||
dbName: "yara724",
|
||||
autoIndex: true,
|
||||
user: process.env.MONGO_USER,
|
||||
pass: process.env.MONGO_PASS,
|
||||
authMechanism: "SCRAM-SHA-256",
|
||||
tls: true,
|
||||
tlsAllowInvalidCertificates: true,
|
||||
},
|
||||
),
|
||||
UsersModule,
|
||||
AuthModule,
|
||||
ClientModule,
|
||||
ProfileModule,
|
||||
PlatesModule,
|
||||
RequestManagementModule,
|
||||
SandHubModule,
|
||||
ExpertBlameModule,
|
||||
ClaimRequestManagementModule,
|
||||
ExpertClaimModule,
|
||||
AiModule,
|
||||
ReportsModule,
|
||||
ExpertInsurerModule,
|
||||
LookupsModule,
|
||||
],
|
||||
controllers: [],
|
||||
providers: [],
|
||||
})
|
||||
export class AppModule {}
|
||||
123
src/auth/auth-controllers/actor/actor.auth.controller.ts
Normal file
123
src/auth/auth-controllers/actor/actor.auth.controller.ts
Normal file
@@ -0,0 +1,123 @@
|
||||
import {
|
||||
Body,
|
||||
Controller,
|
||||
Get,
|
||||
Param,
|
||||
Patch,
|
||||
Post,
|
||||
Req,
|
||||
UseGuards,
|
||||
} from "@nestjs/common";
|
||||
import {
|
||||
ApiBody,
|
||||
ApiAcceptedResponse,
|
||||
ApiResponse,
|
||||
ApiTags,
|
||||
ApiBearerAuth,
|
||||
} from "@nestjs/swagger";
|
||||
import { ActorAuthService } from "src/auth/auth-services/actor.auth.service";
|
||||
import {
|
||||
ForgetPasswordSendCodeDto,
|
||||
ForgetPasswordVerifyCodeDto,
|
||||
} from "src/auth/dto/actor/forget-password.actor.dto";
|
||||
import { LoginActorDto } from "src/auth/dto/actor/login.actor.dto";
|
||||
import { ActorEditUserProfileDto } from "src/auth/dto/actor/profile.actor.dto";
|
||||
import {
|
||||
GenuineRegisterDto,
|
||||
InsurerRegisterDto,
|
||||
LegalRegisterDto,
|
||||
} from "src/auth/dto/actor/register.actor.dto";
|
||||
import { LocalActorAuthGuard } from "src/auth/guards/actor-local.guard";
|
||||
import { ClientKey } from "src/decorators/clientKey.decorator";
|
||||
import { Roles } from "src/decorators/roles.decorator";
|
||||
import { CurrentUser } from "src/decorators/user.decorator";
|
||||
|
||||
@Controller("actor")
|
||||
@ApiTags("actor")
|
||||
export class ActorAuthController {
|
||||
constructor(private readonly actorAuthService: ActorAuthService) {}
|
||||
|
||||
@Post("register/genuine")
|
||||
@ApiBody({ type: GenuineRegisterDto })
|
||||
async registerGenuine(@Body() body: GenuineRegisterDto) {
|
||||
return await this.actorAuthService.genuineRegister(body);
|
||||
}
|
||||
|
||||
@Post("register/legal")
|
||||
@ApiBody({ type: LegalRegisterDto })
|
||||
async registerLegal(@Body() body: LegalRegisterDto) {
|
||||
return await this.actorAuthService.legalRegister(body);
|
||||
}
|
||||
|
||||
@Post("register/insurer")
|
||||
@ApiBody({ type: InsurerRegisterDto })
|
||||
async registerInsurer(@Body() body: InsurerRegisterDto) {
|
||||
return await this.actorAuthService.insurerRegister(body);
|
||||
}
|
||||
|
||||
@UseGuards(LocalActorAuthGuard)
|
||||
@Post("login")
|
||||
@Roles()
|
||||
@ApiBody({
|
||||
type: LoginActorDto,
|
||||
description: "user verify otp -- call this api and get a tokens",
|
||||
})
|
||||
@ApiAcceptedResponse()
|
||||
async login(@Body() body, @Req() req, @ClientKey() client) {
|
||||
return await this.actorAuthService.loginActors(req.user);
|
||||
}
|
||||
|
||||
@Post("forget-password")
|
||||
@ApiBody({
|
||||
type: ForgetPasswordSendCodeDto,
|
||||
description: "send otp when call this api",
|
||||
})
|
||||
@ApiAcceptedResponse()
|
||||
@ApiResponse({ type: ForgetPasswordSendCodeDto })
|
||||
async forgetPassword(@Body() body: ForgetPasswordSendCodeDto) {
|
||||
return await this.actorAuthService.forgetPasswordSendMail(body.email);
|
||||
}
|
||||
|
||||
@Post("forget-password-verify")
|
||||
@ApiBody({
|
||||
type: ForgetPasswordVerifyCodeDto,
|
||||
description: "send otp when call this api",
|
||||
})
|
||||
@ApiAcceptedResponse()
|
||||
@ApiResponse({ type: ForgetPasswordVerifyCodeDto })
|
||||
async forgetPasswordVerify(@Body() body: ForgetPasswordVerifyCodeDto) {
|
||||
const { email, otp, newPassword } = body;
|
||||
return await this.actorAuthService.forgetPasswordVerify(
|
||||
email,
|
||||
otp,
|
||||
newPassword,
|
||||
);
|
||||
}
|
||||
|
||||
@Get("register/form/states/list")
|
||||
async getStates() {
|
||||
return await this.actorAuthService.getStates();
|
||||
}
|
||||
|
||||
@Get("register/form/cities/list/:stateId")
|
||||
async getCities(@Param("stateId") stateId: number) {
|
||||
return await this.actorAuthService.getCities(stateId);
|
||||
}
|
||||
|
||||
@Get("/profile")
|
||||
@UseGuards(LocalActorAuthGuard)
|
||||
@ApiBearerAuth()
|
||||
async getProfile(@CurrentUser() user) {
|
||||
return await this.actorAuthService.getProfiles(user);
|
||||
}
|
||||
|
||||
@Patch("/profile")
|
||||
@UseGuards(LocalActorAuthGuard)
|
||||
@ApiBearerAuth()
|
||||
async editProfile(
|
||||
@CurrentUser() user,
|
||||
@Body() update: ActorEditUserProfileDto,
|
||||
) {
|
||||
return await this.actorAuthService.modifyProfile(user, update);
|
||||
}
|
||||
}
|
||||
45
src/auth/auth-controllers/user/user.auth.controller.ts
Normal file
45
src/auth/auth-controllers/user/user.auth.controller.ts
Normal file
@@ -0,0 +1,45 @@
|
||||
import {
|
||||
Body,
|
||||
Controller,
|
||||
HttpException,
|
||||
HttpStatus,
|
||||
Post,
|
||||
Req,
|
||||
UseGuards,
|
||||
} from "@nestjs/common";
|
||||
import { ApiAcceptedResponse, ApiBody, ApiTags } from "@nestjs/swagger";
|
||||
import { UserAuthService } from "src/auth/auth-services/user.auth.service";
|
||||
import { UserLoginDto } from "src/auth/dto/user/login.dto";
|
||||
import { UserVerifyOtp } from "src/auth/dto/user/verify.dto";
|
||||
import { LocalUserAuthGuard } from "src/auth/guards/user-local.guard";
|
||||
import { CurrentUser } from "src/decorators/user.decorator";
|
||||
|
||||
@Controller("user")
|
||||
@ApiTags("user")
|
||||
export class UserAuthController {
|
||||
constructor(private readonly userAuthService: UserAuthService) {}
|
||||
|
||||
@Post("/send-otp")
|
||||
@ApiBody({
|
||||
type: UserLoginDto,
|
||||
description: "user login api -- call this api and send otp",
|
||||
})
|
||||
@ApiAcceptedResponse()
|
||||
async sendOtpRq(@Body() body: UserLoginDto) {
|
||||
const res = await this.userAuthService.sendOtpRequest(body.mobile);
|
||||
if (res) {
|
||||
throw new HttpException(res, HttpStatus.ACCEPTED);
|
||||
}
|
||||
}
|
||||
|
||||
@Post("/login")
|
||||
@UseGuards(LocalUserAuthGuard)
|
||||
@ApiBody({
|
||||
type: UserVerifyOtp,
|
||||
description: "user verify otp -- call this api and get a tokens",
|
||||
})
|
||||
@ApiAcceptedResponse()
|
||||
async login(@Body() body, @Req() req, @CurrentUser() user) {
|
||||
return await this.userAuthService.login(req.user);
|
||||
}
|
||||
}
|
||||
397
src/auth/auth-services/actor.auth.service.ts
Normal file
397
src/auth/auth-services/actor.auth.service.ts
Normal file
@@ -0,0 +1,397 @@
|
||||
import { readFileSync } from "node:fs";
|
||||
import {
|
||||
BadGatewayException,
|
||||
BadRequestException,
|
||||
ForbiddenException,
|
||||
Injectable,
|
||||
InternalServerErrorException,
|
||||
NotFoundException,
|
||||
UnauthorizedException,
|
||||
} from "@nestjs/common";
|
||||
import { JwtService } from "@nestjs/jwt";
|
||||
import { Types } from "mongoose";
|
||||
import { ForgetPasswordVerifyCodeDtoRs } from "src/auth/dto/actor/forget-password.actor.dto";
|
||||
import { ProfileActor } from "src/auth/dto/actor/profile.actor.dto";
|
||||
import {
|
||||
InsurerRegisterDto,
|
||||
RegisterDto,
|
||||
RegisterDtoRs,
|
||||
} from "src/auth/dto/actor/register.actor.dto";
|
||||
import { StateListDtoRs } from "src/auth/dto/actor/states.dto";
|
||||
import { ClientDbService } from "src/client/entities/db-service/client.db.service";
|
||||
import { RoleEnum } from "src/Types&Enums/role.enum";
|
||||
import { UserType } from "src/Types&Enums/userType.enum";
|
||||
import { InsurerExpertDbService } from "src/users/entities/db-service/insurer-expert.db.service";
|
||||
import { DamageExpertDbService } from "src/users/entities/db-service/damage-expert.db.service";
|
||||
import { ExpertDbService } from "src/users/entities/db-service/expert.db.service";
|
||||
import { HashService } from "src/utils/hash/hash.service";
|
||||
import { MailService } from "src/utils/mail/mail.service";
|
||||
import { OtpService } from "src/utils/otp/otp.service";
|
||||
|
||||
function pick(obj: Record<string, any>, keys: string[]) {
|
||||
const out: Record<string, any> = {};
|
||||
for (const key of keys) {
|
||||
if (obj[key] !== undefined) out[key] = obj[key];
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
// TODO FIX REGISTER TO ACTOR.SERVICE AND AUTH IN THIS MODULE
|
||||
@Injectable()
|
||||
export class ActorAuthService {
|
||||
constructor(
|
||||
private readonly jwtService: JwtService,
|
||||
private readonly hashService: HashService,
|
||||
private readonly expertDbService: ExpertDbService,
|
||||
private readonly damageExpertDbService: DamageExpertDbService,
|
||||
private readonly insurerExpertDbService: InsurerExpertDbService,
|
||||
private readonly mailService: MailService,
|
||||
private readonly clientDbService: ClientDbService,
|
||||
private readonly otpService: OtpService,
|
||||
) {}
|
||||
|
||||
// TODO convrt to class for dynamic controller
|
||||
public async dynamicDbController(role, username, userId?: string) {
|
||||
let res;
|
||||
switch (role) {
|
||||
case RoleEnum.EXPERT:
|
||||
if (username == null && userId)
|
||||
res = await this.expertDbService.findOne({
|
||||
_id: new Types.ObjectId(userId),
|
||||
});
|
||||
else res = await this.expertDbService.findOne({ email: username });
|
||||
break;
|
||||
case RoleEnum.DAMAGE_EXPERT:
|
||||
if (username == null && userId)
|
||||
res = await this.damageExpertDbService.findOne({
|
||||
_id: new Types.ObjectId(userId),
|
||||
});
|
||||
else
|
||||
res = await this.damageExpertDbService.findOne({ email: username });
|
||||
break;
|
||||
case RoleEnum.COMPANY:
|
||||
res = await this.insurerExpertDbService.findOne({ email: username });
|
||||
break;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
async validateActor(username: string, pass: string, role): Promise<any> {
|
||||
const user = await this.dynamicDbController(role, username);
|
||||
if (user) {
|
||||
if (user.role !== role) {
|
||||
throw new UnauthorizedException("user not assigned to this role");
|
||||
}
|
||||
if (!(await this.hashService.compare(pass, user.password))) {
|
||||
throw new UnauthorizedException(
|
||||
"password is incorrect or access Denied",
|
||||
);
|
||||
} else {
|
||||
return user;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
async loginActors(user: any) {
|
||||
let foundedUser = await this.dynamicDbController(user.role, user.username);
|
||||
if (foundedUser) {
|
||||
const payload = {
|
||||
username: foundedUser.username || foundedUser.email,
|
||||
sub: foundedUser._id,
|
||||
fullName:
|
||||
`${foundedUser.firstName || ""} ${foundedUser.lastName || ""}`.trim(),
|
||||
role: foundedUser.role || "User",
|
||||
userType: foundedUser.userType || "UserType",
|
||||
clientKey: foundedUser.clientKey || null,
|
||||
};
|
||||
|
||||
const accToken = this.jwtService.sign(payload, {
|
||||
secret: `${process.env.SECRET}`,
|
||||
expiresIn: "1h",
|
||||
});
|
||||
|
||||
return {
|
||||
...payload,
|
||||
access_token: accToken,
|
||||
};
|
||||
} else {
|
||||
throw new UnauthorizedException("expert or damage_expert not found");
|
||||
}
|
||||
}
|
||||
|
||||
async registerActors(
|
||||
body: RegisterDto | InsurerRegisterDto,
|
||||
userType: UserType,
|
||||
) {
|
||||
const hashPassword = await this.hashService.hash(body.password);
|
||||
body.password = hashPassword;
|
||||
|
||||
(body as any).userType = userType;
|
||||
|
||||
try {
|
||||
if ("username" in body) {
|
||||
if (userType === UserType.INSURER) {
|
||||
if (!body.clientKey) {
|
||||
throw new BadRequestException(
|
||||
"clientKey is required for this user type.",
|
||||
);
|
||||
}
|
||||
const clientName = await this.clientDbService.find({
|
||||
_id: new Types.ObjectId(body.clientKey),
|
||||
});
|
||||
if (!clientName) throw new NotFoundException("Client Not Found");
|
||||
}
|
||||
|
||||
const newCompanyPayload = {
|
||||
email: body.username,
|
||||
password: body.password,
|
||||
role: RoleEnum.COMPANY,
|
||||
userType: (body as any).userType,
|
||||
clientKey: body.clientKey,
|
||||
firstName: body.firstName,
|
||||
lastName: body.lastName,
|
||||
};
|
||||
|
||||
return new RegisterDtoRs(
|
||||
await this.insurerExpertDbService.create(newCompanyPayload),
|
||||
);
|
||||
} else {
|
||||
if (userType === UserType.LEGAL || userType === UserType.GENUINE) {
|
||||
if (!body.insuActivityCo) {
|
||||
throw new BadRequestException(
|
||||
"insuActivityCo is required for this user type.",
|
||||
);
|
||||
}
|
||||
const clientName = await this.clientDbService.find({
|
||||
_id: body.insuActivityCo,
|
||||
});
|
||||
if (!clientName) throw new NotFoundException("Client Not Found");
|
||||
|
||||
body.clientKey = body.insuActivityCo;
|
||||
body.insuActivityCo = clientName.clientName.english;
|
||||
}
|
||||
|
||||
switch (body.role) {
|
||||
case RoleEnum.EXPERT:
|
||||
return new RegisterDtoRs(await this.expertDbService.create(body));
|
||||
case RoleEnum.DAMAGE_EXPERT:
|
||||
return new RegisterDtoRs(
|
||||
await this.damageExpertDbService.create(body),
|
||||
);
|
||||
default:
|
||||
throw new BadRequestException(
|
||||
`Invalid role for this registration type: ${body.role}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
} catch (er) {
|
||||
if (er.code === 11000) {
|
||||
throw new BadRequestException(
|
||||
"A user with these details already exists.",
|
||||
);
|
||||
} else if (er.errors) {
|
||||
const errObjKey = Object.keys(er.errors)[0];
|
||||
const { path, kind } = er.errors[errObjKey];
|
||||
throw new BadRequestException(
|
||||
`Validation failed for field '${path}'. Expected a valid ${kind}.`,
|
||||
);
|
||||
} else {
|
||||
throw er;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async legalRegister(body: RegisterDto) {
|
||||
return await this.registerActors(body, UserType.LEGAL);
|
||||
}
|
||||
|
||||
async genuineRegister(body: RegisterDto) {
|
||||
return await this.registerActors(body, UserType.GENUINE);
|
||||
}
|
||||
|
||||
async insurerRegister(body: InsurerRegisterDto) {
|
||||
Object.assign(body, { role: RoleEnum.COMPANY });
|
||||
return await this.registerActors(body, UserType.INSURER);
|
||||
}
|
||||
|
||||
/// TODO need to seed
|
||||
async getStates() {
|
||||
const states = JSON.parse(
|
||||
readFileSync(`${process.cwd()}/src/static/states.json`, "utf8"),
|
||||
);
|
||||
return new StateListDtoRs(states);
|
||||
}
|
||||
|
||||
/// TODO need to seeds
|
||||
async getCities(id: number) {
|
||||
const cities = JSON.parse(
|
||||
readFileSync(`${process.cwd()}/src/static/cities.json`, "utf8"),
|
||||
);
|
||||
const citiesList = cities.filter((c) => c.province_id == id);
|
||||
return new StateListDtoRs(citiesList);
|
||||
}
|
||||
|
||||
async forgetPasswordSendMail(email: string) {
|
||||
const normalizedEmail = email.toLowerCase().trim();
|
||||
const filter = { email: normalizedEmail };
|
||||
|
||||
let actor;
|
||||
|
||||
// expert
|
||||
actor = await this.expertDbService.findOne(filter);
|
||||
let dbServiceToUpdate = this.expertDbService;
|
||||
|
||||
if (!actor) {
|
||||
actor = (await this.damageExpertDbService.findOne(filter)) as any;
|
||||
dbServiceToUpdate = this.damageExpertDbService as any;
|
||||
}
|
||||
|
||||
if (!actor) {
|
||||
actor = await this.insurerExpertDbService.findOne(filter);
|
||||
dbServiceToUpdate = this.insurerExpertDbService as any;
|
||||
}
|
||||
|
||||
if (!actor) {
|
||||
throw new NotFoundException("actor not found");
|
||||
}
|
||||
|
||||
const otp = this.otpService.create();
|
||||
const sendEmail = await this.mailService.sendMail(normalizedEmail, otp);
|
||||
|
||||
if (sendEmail) {
|
||||
const hashOtp = await this.hashService.hash(otp);
|
||||
|
||||
await dbServiceToUpdate.findOneAndUpdate(filter, { otp: hashOtp });
|
||||
|
||||
return sendEmail;
|
||||
} else {
|
||||
throw new BadGatewayException("mailServer in unavailable");
|
||||
}
|
||||
}
|
||||
|
||||
async forgetPasswordVerify(email, otp, newPassword) {
|
||||
const userExist = await this.expertDbService.findOne({ email });
|
||||
if (!userExist) throw new NotFoundException("user not found");
|
||||
const decodeOtp = await this.hashService.compare(otp, userExist.otp);
|
||||
if (!decodeOtp) throw new UnauthorizedException("otp invalid");
|
||||
if (decodeOtp) {
|
||||
const hashNewPassword = await this.hashService.hash(newPassword);
|
||||
await this.expertDbService.findOneAndUpdate(
|
||||
{ email },
|
||||
{ password: hashNewPassword },
|
||||
);
|
||||
return new ForgetPasswordVerifyCodeDtoRs(userExist, "update password");
|
||||
}
|
||||
}
|
||||
|
||||
async getProfiles(currentUser) {
|
||||
const userDetail = await this.dynamicDbController(
|
||||
currentUser.role,
|
||||
null,
|
||||
currentUser.sub,
|
||||
);
|
||||
return new ProfileActor(userDetail);
|
||||
}
|
||||
|
||||
async modifyProfile(currentUser: any, update: Record<string, any>) {
|
||||
const role = currentUser?.role;
|
||||
const userId = currentUser?.sub;
|
||||
|
||||
if (!role || !userId) {
|
||||
throw new BadRequestException("Invalid actor payload");
|
||||
}
|
||||
|
||||
const allowedFieldsByRole: Record<string, string[]> = {
|
||||
expert: [
|
||||
"fullName",
|
||||
"firstName",
|
||||
"lastName",
|
||||
"email",
|
||||
"phone",
|
||||
"city",
|
||||
"state",
|
||||
"bio",
|
||||
"nationalCode",
|
||||
"birthDate",
|
||||
"avatarUrl",
|
||||
"address",
|
||||
],
|
||||
damage_expert: [
|
||||
"fullName",
|
||||
"email",
|
||||
"phone",
|
||||
"companyName",
|
||||
"city",
|
||||
"state",
|
||||
"bio",
|
||||
"nationalCode",
|
||||
"birthDate",
|
||||
"avatarUrl",
|
||||
"address",
|
||||
],
|
||||
};
|
||||
|
||||
const allowedFields = allowedFieldsByRole[role];
|
||||
if (!allowedFields) {
|
||||
throw new ForbiddenException("Profile update not allowed for this role");
|
||||
}
|
||||
|
||||
const sanitized = Object.fromEntries(
|
||||
Object.entries(update).filter(([key]) => allowedFields.includes(key)),
|
||||
);
|
||||
|
||||
if (Object.keys(sanitized).length === 0) {
|
||||
throw new BadRequestException("No valid fields to update");
|
||||
}
|
||||
|
||||
if (sanitized.birthDate && typeof sanitized.birthDate === "string") {
|
||||
const parsed = new Date(sanitized.birthDate);
|
||||
if (isNaN(parsed.getTime())) {
|
||||
throw new BadRequestException("birthDate must be a valid date");
|
||||
}
|
||||
sanitized.birthDate = parsed;
|
||||
}
|
||||
|
||||
// fetch user detail (document or plain object)
|
||||
const document = await this.dynamicDbController(role, null, userId);
|
||||
|
||||
if (!document) throw new NotFoundException("Profile not found");
|
||||
|
||||
try {
|
||||
// if it’s a mongoose document (has .save)
|
||||
if (typeof document.save === "function") {
|
||||
Object.assign(document, sanitized);
|
||||
await document.save();
|
||||
return document;
|
||||
}
|
||||
|
||||
// else, update directly via corresponding DB service
|
||||
switch (role) {
|
||||
case "expert":
|
||||
await this.expertDbService.updateOne(
|
||||
{ _id: new Types.ObjectId(userId) },
|
||||
{ $set: sanitized },
|
||||
);
|
||||
break;
|
||||
|
||||
case "damage_expert":
|
||||
await this.damageExpertDbService.updateOne(
|
||||
{ _id: new Types.ObjectId(userId) },
|
||||
{ $set: sanitized },
|
||||
);
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new ForbiddenException("Unsupported role for update");
|
||||
}
|
||||
|
||||
// return the fresh document
|
||||
return await this.dynamicDbController(role, null, userId);
|
||||
} catch (err) {
|
||||
throw new InternalServerErrorException("Failed to update profile");
|
||||
}
|
||||
}
|
||||
}
|
||||
137
src/auth/auth-services/user.auth.service.ts
Normal file
137
src/auth/auth-services/user.auth.service.ts
Normal file
@@ -0,0 +1,137 @@
|
||||
import {
|
||||
HttpException,
|
||||
HttpStatus,
|
||||
Injectable,
|
||||
Logger,
|
||||
NotAcceptableException,
|
||||
NotFoundException,
|
||||
} from "@nestjs/common";
|
||||
import { JwtService } from "@nestjs/jwt";
|
||||
import { Types } from "mongoose";
|
||||
import { LoginDtoRs } from "src/auth/dto/user/login.dto";
|
||||
import { UserDbService } from "src/users/entities/db-service/user.db.service";
|
||||
import { HashService } from "src/utils/hash/hash.service";
|
||||
import { OtpService } from "src/utils/otp/otp.service";
|
||||
import { SmsManagerService } from "src/utils/sms-manager/sms-manager.service";
|
||||
|
||||
// TODO FIX REGISTER TO USER.SERVICE AND AUTH IN THIS MODULE
|
||||
@Injectable()
|
||||
export class UserAuthService {
|
||||
private readonly logger = new Logger(UserAuthService.name);
|
||||
|
||||
constructor(
|
||||
private readonly jwtService: JwtService,
|
||||
private readonly userDbService: UserDbService,
|
||||
private readonly hashService: HashService,
|
||||
private readonly otpCreator: OtpService,
|
||||
private readonly smsManagerService: SmsManagerService,
|
||||
) {}
|
||||
|
||||
async validateUser(username: string, pass: string): Promise<any> {
|
||||
const user = await this.userDbService.findOne({ username });
|
||||
if (!user) throw new NotFoundException("user not found");
|
||||
|
||||
const now = new Date().getTime();
|
||||
if (user.otp == null) throw new NotAcceptableException("please get otp");
|
||||
if (user.otpExpire < now) {
|
||||
throw new NotAcceptableException("expire otp");
|
||||
}
|
||||
if (await this.hashService.compare(pass, user.otp)) {
|
||||
return user;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
async login(user: any) {
|
||||
const payload = {
|
||||
username: user.username,
|
||||
sub: user.id,
|
||||
role: "user",
|
||||
};
|
||||
const accToken = this.jwtService.sign(payload, {
|
||||
secret: `${process.env.SECRET}`,
|
||||
});
|
||||
await this.userDbService.findOneAndUpdate(
|
||||
{ username: user.username },
|
||||
{
|
||||
tokens: { token: accToken },
|
||||
otp: null,
|
||||
},
|
||||
);
|
||||
return {
|
||||
userId: user._id,
|
||||
access_token: accToken,
|
||||
};
|
||||
}
|
||||
|
||||
async sendOtpRequest(mobile: string): Promise<LoginDtoRs> {
|
||||
const userExist = await this.userDbService.findOne({
|
||||
mobile,
|
||||
});
|
||||
const otp = this.otpCreator.create();
|
||||
const hashOtp = await this.hashService.hash(otp);
|
||||
if (!userExist) {
|
||||
await this.smsSender(otp, mobile);
|
||||
/// create otp request
|
||||
const newUser = await this.userDbService.createUser({
|
||||
mobile,
|
||||
username: mobile,
|
||||
otp: hashOtp,
|
||||
tokens: {
|
||||
token: "",
|
||||
rfToken: "",
|
||||
},
|
||||
fullName: "",
|
||||
nationalCode: "",
|
||||
lastLogin: new Date(),
|
||||
clientKey: new Types.ObjectId(),
|
||||
birthDay: "",
|
||||
city: "",
|
||||
address: "",
|
||||
state: "",
|
||||
otpExpire: new Date(
|
||||
new Date().getTime() + +process.env.EXP_OTP_TIME * 60 * 1000,
|
||||
).getTime(),
|
||||
});
|
||||
return new LoginDtoRs(newUser);
|
||||
}
|
||||
if (userExist) {
|
||||
await this.smsSender(otp, mobile);
|
||||
const updateTokens = await this.userDbService.findOneAndUpdate(
|
||||
{
|
||||
username: userExist.username,
|
||||
},
|
||||
{
|
||||
otp: hashOtp,
|
||||
otpExpire: new Date(
|
||||
new Date().getTime() + +process.env.EXP_OTP_TIME * 60 * 1000,
|
||||
).getTime(),
|
||||
},
|
||||
);
|
||||
if (updateTokens) return new LoginDtoRs(userExist);
|
||||
}
|
||||
}
|
||||
|
||||
private async smsSender(otp: string, mobile: string) {
|
||||
return this.smsManagerService
|
||||
.verifyLookUp({
|
||||
token: otp,
|
||||
template: process.env.AUTH_SMS_TEMPLATE,
|
||||
receptor: mobile,
|
||||
})
|
||||
.then((smsRes) => {
|
||||
this.logger.log(
|
||||
`${"phone : " + mobile + " " + ", status : " + smsRes["return"].status + ", otp : " + otp} `,
|
||||
);
|
||||
})
|
||||
.catch((er) => {
|
||||
this.logger.error(
|
||||
`${"phone : " + mobile + " " + ", status : " + er["return"].status + ", otp : " + otp} `,
|
||||
);
|
||||
throw new HttpException(
|
||||
" auth sms send failed",
|
||||
HttpStatus.INTERNAL_SERVER_ERROR,
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
42
src/auth/auth.module.ts
Normal file
42
src/auth/auth.module.ts
Normal file
@@ -0,0 +1,42 @@
|
||||
import { Module } from "@nestjs/common";
|
||||
import { JwtModule, JwtService } from "@nestjs/jwt";
|
||||
import { PassportModule } from "@nestjs/passport";
|
||||
import { LocalStrategy } from "src/auth/stratregys/local.strategy";
|
||||
import { LocalActorStrategy } from "src/auth/stratregys/local-actor.strategy";
|
||||
import { ActorAuthController } from "src/auth/auth-controllers/actor/actor.auth.controller";
|
||||
import { UserAuthController } from "src/auth/auth-controllers/user/user.auth.controller";
|
||||
import { ActorAuthService } from "src/auth/auth-services/actor.auth.service";
|
||||
import { UserAuthService } from "src/auth/auth-services/user.auth.service";
|
||||
import { ClientModule } from "src/client/client.module";
|
||||
import { UsersModule } from "src/users/users.module";
|
||||
import { HashModule } from "src/utils/hash/hash.module";
|
||||
import { MailModule } from "src/utils/mail/mail.module";
|
||||
import { OtpModule } from "src/utils/otp/otp.module";
|
||||
import { SmsManagerModule } from "src/utils/sms-manager/sms-manager.module";
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
MailModule,
|
||||
UsersModule,
|
||||
ClientModule,
|
||||
HashModule,
|
||||
OtpModule,
|
||||
PassportModule,
|
||||
SmsManagerModule,
|
||||
JwtModule.register({
|
||||
signOptions: { expiresIn: "1h" },
|
||||
global: true,
|
||||
secret: `${process.env.SECRET}`,
|
||||
}),
|
||||
],
|
||||
providers: [
|
||||
UserAuthService,
|
||||
ActorAuthService,
|
||||
LocalStrategy,
|
||||
LocalActorStrategy,
|
||||
JwtService,
|
||||
],
|
||||
exports: [LocalStrategy, UserAuthService, ActorAuthService, JwtService],
|
||||
controllers: [UserAuthController, ActorAuthController],
|
||||
})
|
||||
export class AuthModule {}
|
||||
30
src/auth/dto/actor/forget-password.actor.dto.ts
Normal file
30
src/auth/dto/actor/forget-password.actor.dto.ts
Normal file
@@ -0,0 +1,30 @@
|
||||
import { ApiProperty } from "@nestjs/swagger";
|
||||
|
||||
export class ForgetPasswordSendCodeDto {
|
||||
@ApiProperty({ example: "balali.arash@gmail.com", type: String })
|
||||
email: string;
|
||||
}
|
||||
|
||||
export class ForgetPasswordVerifyCodeDto {
|
||||
@ApiProperty({ example: "09331009989", type: String })
|
||||
email: string;
|
||||
|
||||
@ApiProperty({ type: String, examples: { true: "22222" } })
|
||||
otp: string;
|
||||
|
||||
@ApiProperty({ type: String })
|
||||
newPassword: string;
|
||||
}
|
||||
|
||||
export class ForgetPasswordVerifyCodeDtoRs {
|
||||
@ApiProperty({ example: "balali.arash@gmail.com", type: String })
|
||||
email: string;
|
||||
|
||||
@ApiProperty({ type: String, examples: { true: "22222" } })
|
||||
message: string;
|
||||
|
||||
constructor(user, message) {
|
||||
this.email = user.email;
|
||||
this.message = message;
|
||||
}
|
||||
}
|
||||
41
src/auth/dto/actor/login.actor.dto.ts
Normal file
41
src/auth/dto/actor/login.actor.dto.ts
Normal file
@@ -0,0 +1,41 @@
|
||||
import { ApiProperty } from "@nestjs/swagger";
|
||||
import { RoleEnum } from "src/Types&Enums/role.enum";
|
||||
|
||||
export class LoginActorDto {
|
||||
@ApiProperty({ example: RoleEnum, type: "array", description: "LOGIN_DTO" })
|
||||
role: RoleEnum[];
|
||||
|
||||
@ApiProperty({})
|
||||
username: string;
|
||||
|
||||
@ApiProperty({})
|
||||
password: string;
|
||||
}
|
||||
|
||||
export class LoginActorDtoRs extends LoginActorDto {
|
||||
private readonly userId;
|
||||
constructor(userData) {
|
||||
super();
|
||||
this.userId = userData._id;
|
||||
this.role = userData.role;
|
||||
this.username = userData.email;
|
||||
this.clientKey = userData.clientKey;
|
||||
this.token = userData.token;
|
||||
this.refreshToken = userData.refreshToken;
|
||||
}
|
||||
|
||||
@ApiProperty({ type: "string", description: "LOGIN_DTO_RS" })
|
||||
fullName: string;
|
||||
|
||||
@ApiProperty({ type: "string", description: "LOGIN_DTO_RS" })
|
||||
role: RoleEnum[];
|
||||
|
||||
@ApiProperty({ type: "string", description: "LOGIN_DTO_RS" })
|
||||
token: string;
|
||||
|
||||
@ApiProperty({ type: "string", description: "LOGIN_DTO_RS" })
|
||||
refreshToken: string;
|
||||
|
||||
@ApiProperty({ type: "string", description: "LOGIN_DTO_RS" })
|
||||
clientKey: string;
|
||||
}
|
||||
98
src/auth/dto/actor/profile.actor.dto.ts
Normal file
98
src/auth/dto/actor/profile.actor.dto.ts
Normal file
@@ -0,0 +1,98 @@
|
||||
import { ApiProperty, ApiSchema } from "@nestjs/swagger";
|
||||
import { IsMobilePhone, IsString, Length } from "class-validator";
|
||||
import { DamageExpertModel } from "src/users/entities/schema/damage-expert.schema";
|
||||
|
||||
export class ProfileActor {
|
||||
public email: string;
|
||||
public fullName: string;
|
||||
public nationalCode: string;
|
||||
public insuActivityCo: string;
|
||||
public userType: string;
|
||||
public mobile: string;
|
||||
public address: string;
|
||||
public state: string;
|
||||
public city: string;
|
||||
|
||||
constructor(Profile: DamageExpertModel) {
|
||||
this.email = Profile?.email;
|
||||
this.fullName = Profile?.firstName + " " + Profile?.lastName;
|
||||
this.nationalCode = Profile?.nationalCode;
|
||||
this.insuActivityCo = Profile?.insuActivityCo;
|
||||
this.userType = Profile?.userType;
|
||||
this.mobile = Profile?.mobile;
|
||||
this.address = Profile?.address;
|
||||
this.state = Profile?.state;
|
||||
this.city = Profile?.city;
|
||||
}
|
||||
}
|
||||
|
||||
@ApiSchema({
|
||||
name: "Edit Actor Profile",
|
||||
description: "Body of the request to edit the actor's profile",
|
||||
})
|
||||
export class ActorEditUserProfileDto {
|
||||
@ApiProperty({
|
||||
type: "string",
|
||||
description: "First name of the actor",
|
||||
example: "heshmat",
|
||||
nullable: true,
|
||||
})
|
||||
@IsString()
|
||||
@Length(2, 40)
|
||||
firstName?: string;
|
||||
|
||||
@ApiProperty({
|
||||
type: "string",
|
||||
description: "Last name of the actor",
|
||||
example: "Heshmati",
|
||||
nullable: true,
|
||||
})
|
||||
@IsString()
|
||||
@Length(1, 10)
|
||||
lastName?: string;
|
||||
|
||||
@ApiProperty({
|
||||
type: "string",
|
||||
description: "Mobile phone of the actor",
|
||||
example: "09123456789",
|
||||
nullable: true,
|
||||
})
|
||||
@IsMobilePhone("fa-IR")
|
||||
mobile?: string;
|
||||
|
||||
@ApiProperty({
|
||||
type: "string",
|
||||
description: "تلفن خط ثابت",
|
||||
example: "02122222222",
|
||||
nullable: true,
|
||||
})
|
||||
@IsString()
|
||||
phone?: string;
|
||||
|
||||
@ApiProperty({
|
||||
type: "string",
|
||||
description: "City of the user",
|
||||
example: "استان",
|
||||
nullable: true,
|
||||
})
|
||||
@IsString()
|
||||
city?: string;
|
||||
|
||||
@ApiProperty({
|
||||
type: "string",
|
||||
description: "State of the user",
|
||||
example: "شهر",
|
||||
nullable: true,
|
||||
})
|
||||
@IsString()
|
||||
state?: string;
|
||||
|
||||
@ApiProperty({
|
||||
type: "string",
|
||||
description: "Address of the user",
|
||||
example: "آدرس",
|
||||
nullable: true,
|
||||
})
|
||||
@IsString()
|
||||
address?: string;
|
||||
}
|
||||
341
src/auth/dto/actor/register.actor.dto.ts
Normal file
341
src/auth/dto/actor/register.actor.dto.ts
Normal file
@@ -0,0 +1,341 @@
|
||||
import { ApiProperty } from "@nestjs/swagger";
|
||||
import { Exclude } from "class-transformer";
|
||||
import { IsEmail } from "class-validator";
|
||||
import { Types } from "mongoose";
|
||||
import { Degrees } from "src/Types&Enums/degrees.enum";
|
||||
import {
|
||||
ExpertizedAtEnum,
|
||||
PreviousWorkEnum,
|
||||
SkillEnum,
|
||||
} from "src/Types&Enums/damage-expert.enum";
|
||||
import { RoleEnum } from "src/Types&Enums/role.enum";
|
||||
import { UserType } from "src/Types&Enums/userType.enum";
|
||||
|
||||
@Exclude()
|
||||
export class RegisterDto {
|
||||
clientKey?: string;
|
||||
@ApiProperty({
|
||||
example: "Soheil",
|
||||
type: "string",
|
||||
description: "firstname of actor",
|
||||
})
|
||||
firstName: string;
|
||||
|
||||
@ApiProperty({
|
||||
enum: RoleEnum,
|
||||
examples: RoleEnum,
|
||||
type: "string",
|
||||
description: "firstname of actor",
|
||||
})
|
||||
role: RoleEnum;
|
||||
|
||||
userType: UserType;
|
||||
|
||||
@ApiProperty({
|
||||
example: "Hajizadeh",
|
||||
type: "string",
|
||||
description: "lastname of actor",
|
||||
})
|
||||
lastName: string;
|
||||
|
||||
@ApiProperty({
|
||||
example: "4311402422",
|
||||
type: "string",
|
||||
description: "nationalCode of actor",
|
||||
})
|
||||
nationalCode: string;
|
||||
|
||||
@ApiProperty({
|
||||
example: "dev.callmeskylark@gmail.com",
|
||||
type: "string",
|
||||
description: "email of actor",
|
||||
})
|
||||
email: string;
|
||||
|
||||
@ApiProperty({
|
||||
example: "123321",
|
||||
type: "string",
|
||||
description: "password of actor",
|
||||
})
|
||||
password: string;
|
||||
|
||||
@ApiProperty({
|
||||
example: "7522312365495123",
|
||||
type: "string",
|
||||
description: "sheba of actor",
|
||||
})
|
||||
sheba?: string;
|
||||
|
||||
@ApiProperty({
|
||||
example: "02133564521",
|
||||
type: "string",
|
||||
description: "phone of actor",
|
||||
})
|
||||
phone?: string;
|
||||
|
||||
@ApiProperty({
|
||||
example: "09226187419",
|
||||
type: "string",
|
||||
description: "mobile of actor",
|
||||
})
|
||||
mobile: string;
|
||||
|
||||
@ApiProperty({
|
||||
example: "Tehran",
|
||||
type: "string",
|
||||
description: "province of actor",
|
||||
})
|
||||
state?: string;
|
||||
|
||||
@ApiProperty({
|
||||
example: "Tehran",
|
||||
type: "string",
|
||||
description: "city of actor",
|
||||
})
|
||||
city?: string;
|
||||
|
||||
@ApiProperty({
|
||||
example: "Gisha , No 7",
|
||||
type: "string",
|
||||
description: "address of actor",
|
||||
})
|
||||
address?: string;
|
||||
|
||||
@ApiProperty({
|
||||
enum: Degrees,
|
||||
examples: Degrees,
|
||||
type: String,
|
||||
description: "expDegree of actor",
|
||||
})
|
||||
expDegree?: Degrees;
|
||||
|
||||
@ApiProperty({
|
||||
example: "5",
|
||||
type: "number",
|
||||
description: "insuActivityTime of actor",
|
||||
})
|
||||
insuActivityTime?: number;
|
||||
|
||||
@ApiProperty({
|
||||
example: "64fc4978b74d670939b08920",
|
||||
type: String,
|
||||
description: "insuActivityCo of actor",
|
||||
})
|
||||
insuActivityCo: string;
|
||||
|
||||
@ApiProperty({
|
||||
example: "5",
|
||||
type: "number",
|
||||
description: "policeActivityTime of actor",
|
||||
})
|
||||
policeActivityTime?: number;
|
||||
|
||||
@ApiProperty({
|
||||
examples: ["headquarters", "queue"],
|
||||
type: "string",
|
||||
description: "policeActivityKind of actor",
|
||||
})
|
||||
policeActivityKind?: string;
|
||||
|
||||
@ApiProperty({
|
||||
example: "Tehran",
|
||||
type: String,
|
||||
description: "policeServicePlace of actor",
|
||||
})
|
||||
policeServicePlace?: string;
|
||||
}
|
||||
|
||||
export class GenuineRegisterDto extends RegisterDto {
|
||||
@ApiProperty({
|
||||
type: () => ({
|
||||
fullName: { type: "string", example: "John Doe" },
|
||||
nationalCode: { type: "string", example: "4311402422" },
|
||||
dob: {
|
||||
type: "string",
|
||||
example: "1990-01-01",
|
||||
description: "date of birth in ISO string or yyyy-mm-dd",
|
||||
},
|
||||
mobile: { type: "string", example: "09226187419" },
|
||||
}),
|
||||
description: "personal information of damage expert (duplicated with some flat fields)",
|
||||
required: false,
|
||||
})
|
||||
personalInfo?: {
|
||||
fullName: string;
|
||||
nationalCode: string;
|
||||
dob: string;
|
||||
mobile: string;
|
||||
};
|
||||
|
||||
@ApiProperty({
|
||||
type: () => ({
|
||||
nationalCard: {
|
||||
type: "string",
|
||||
example: "665f0e5ab74d670939b08920",
|
||||
description: "fileId of uploaded national card",
|
||||
},
|
||||
selfie: {
|
||||
type: "string",
|
||||
example: "665f0e5ab74d670939b08921",
|
||||
description: "fileId of uploaded selfie",
|
||||
},
|
||||
activityCert: {
|
||||
type: "string",
|
||||
example: "665f0e5ab74d670939b08922",
|
||||
description: "fileId of uploaded activity certificate",
|
||||
},
|
||||
}),
|
||||
description:
|
||||
"IDs of already uploaded documents in upload module (no files uploaded directly here)",
|
||||
required: false,
|
||||
})
|
||||
personalDocs?: {
|
||||
nationalCard?: string;
|
||||
selfie?: string;
|
||||
activityCert?: string;
|
||||
};
|
||||
|
||||
@ApiProperty({
|
||||
type: () => ({
|
||||
state: {
|
||||
type: "number",
|
||||
example: 8,
|
||||
description: "state id from /actor/register/form/states/list",
|
||||
},
|
||||
city: {
|
||||
type: "number",
|
||||
example: 101,
|
||||
description: "city id from /actor/register/form/cities/list/:stateId",
|
||||
},
|
||||
expertizedAt: {
|
||||
type: "array",
|
||||
items: { enum: Object.values(ExpertizedAtEnum) },
|
||||
example: [ExpertizedAtEnum.BADANE, ExpertizedAtEnum.FANI],
|
||||
description: "one or more expertized fields",
|
||||
},
|
||||
}),
|
||||
required: false,
|
||||
})
|
||||
workExperience?: {
|
||||
state: number;
|
||||
city: number;
|
||||
expertizedAt: ExpertizedAtEnum[];
|
||||
};
|
||||
|
||||
@ApiProperty({
|
||||
type: () => ({
|
||||
workingYears: {
|
||||
type: "number",
|
||||
example: 5,
|
||||
},
|
||||
casesCount: {
|
||||
type: "number",
|
||||
example: 120,
|
||||
},
|
||||
previousWork: {
|
||||
enum: PreviousWorkEnum,
|
||||
example: PreviousWorkEnum.INSURANCE_COMPANY,
|
||||
},
|
||||
}),
|
||||
required: false,
|
||||
})
|
||||
workRecords?: {
|
||||
workingYears: number;
|
||||
casesCount: number;
|
||||
previousWork: PreviousWorkEnum;
|
||||
};
|
||||
|
||||
@ApiProperty({
|
||||
type: "array",
|
||||
items: {
|
||||
type: "string",
|
||||
enum: Object.values(SkillEnum),
|
||||
},
|
||||
required: false,
|
||||
description: "one or more skills of the damage expert",
|
||||
example: [SkillEnum.SMOOTHING, SkillEnum.SCENE_EXPERT],
|
||||
})
|
||||
skills?: SkillEnum[];
|
||||
|
||||
@ApiProperty({
|
||||
type: () => ({
|
||||
IBAN: {
|
||||
type: "string",
|
||||
example: "IR820540102680020817909002",
|
||||
},
|
||||
cardNum: {
|
||||
type: "string",
|
||||
example: "6037991234567890",
|
||||
},
|
||||
accountNum: {
|
||||
type: "string",
|
||||
example: "0101234567000",
|
||||
},
|
||||
}),
|
||||
required: false,
|
||||
})
|
||||
financialInfo?: {
|
||||
IBAN: string;
|
||||
cardNum: string;
|
||||
accountNum: string;
|
||||
};
|
||||
}
|
||||
|
||||
export class LegalRegisterDto implements RegisterDto {
|
||||
userType: UserType;
|
||||
@ApiProperty()
|
||||
firstName: string;
|
||||
|
||||
@ApiProperty()
|
||||
role: RoleEnum;
|
||||
|
||||
@ApiProperty()
|
||||
lastName: string;
|
||||
|
||||
@ApiProperty()
|
||||
nationalCode: string;
|
||||
|
||||
@ApiProperty()
|
||||
email: string;
|
||||
|
||||
@ApiProperty()
|
||||
password: string;
|
||||
|
||||
@ApiProperty()
|
||||
mobile: string;
|
||||
|
||||
@ApiProperty({
|
||||
example: "64fc4978b74d670939b08920",
|
||||
type: String,
|
||||
description: "insuActivityCo of actor",
|
||||
})
|
||||
insuActivityCo: string;
|
||||
}
|
||||
|
||||
export class InsurerRegisterDto {
|
||||
@ApiProperty()
|
||||
firstName: string;
|
||||
|
||||
@ApiProperty()
|
||||
lastName: string;
|
||||
|
||||
@ApiProperty({ description: "just submit by Email" })
|
||||
@IsEmail()
|
||||
username: string;
|
||||
|
||||
@ApiProperty()
|
||||
password: string;
|
||||
|
||||
@ApiProperty({ example: "{saman : 651abe5814ed4bb6ee20cd81}" })
|
||||
clientKey: Types.ObjectId;
|
||||
}
|
||||
|
||||
export class RegisterDtoRs extends RegisterDto {
|
||||
@ApiProperty({ type: "string", description: "REGISTER_DTO_RS" })
|
||||
message: string;
|
||||
|
||||
constructor(registerData) {
|
||||
super();
|
||||
this.message = registerData.message || "ثبت نام با موفقیت انجام شد.";
|
||||
}
|
||||
}
|
||||
15
src/auth/dto/actor/states.dto.ts
Normal file
15
src/auth/dto/actor/states.dto.ts
Normal file
@@ -0,0 +1,15 @@
|
||||
export class StatesDtoRs {
|
||||
name: string;
|
||||
id: string;
|
||||
constructor(states) {
|
||||
this.name = states.name;
|
||||
this.id = states.id;
|
||||
}
|
||||
}
|
||||
|
||||
export class StateListDtoRs {
|
||||
list: StatesDtoRs[];
|
||||
constructor(statesLis: []) {
|
||||
this.list = statesLis.map((s) => new StatesDtoRs(s));
|
||||
}
|
||||
}
|
||||
0
src/auth/dto/user/forget-password.dto.ts
Normal file
0
src/auth/dto/user/forget-password.dto.ts
Normal file
44
src/auth/dto/user/login.dto.ts
Normal file
44
src/auth/dto/user/login.dto.ts
Normal file
@@ -0,0 +1,44 @@
|
||||
import { ApiProperty } from "@nestjs/swagger";
|
||||
import { UserModel } from "src/users/entities/schema/user.schema";
|
||||
|
||||
export class UserLoginDto {
|
||||
@ApiProperty({
|
||||
example: "09226187419",
|
||||
type: "string",
|
||||
description: "User login dto",
|
||||
})
|
||||
mobile: string;
|
||||
}
|
||||
|
||||
export class LoginDtoRs extends UserModel {
|
||||
message: string;
|
||||
@ApiProperty({
|
||||
example: "09226187419",
|
||||
type: "string",
|
||||
description: "User login dto",
|
||||
})
|
||||
tokens: { token: string; rfToken: string };
|
||||
|
||||
@ApiProperty({
|
||||
example: "09226187419",
|
||||
type: "string",
|
||||
description: "User login dto",
|
||||
})
|
||||
userId: string;
|
||||
|
||||
@ApiProperty({
|
||||
example: "09226187419",
|
||||
type: "string",
|
||||
description: "User login dto",
|
||||
})
|
||||
firstName: string;
|
||||
|
||||
lastName: string;
|
||||
username: string;
|
||||
mobile: string;
|
||||
nationalCode: string;
|
||||
constructor(loginData) {
|
||||
super();
|
||||
this.mobile = loginData.mobile;
|
||||
}
|
||||
}
|
||||
17
src/auth/dto/user/verify.dto.ts
Normal file
17
src/auth/dto/user/verify.dto.ts
Normal file
@@ -0,0 +1,17 @@
|
||||
import { ApiProperty } from "@nestjs/swagger";
|
||||
|
||||
export class UserVerifyOtp {
|
||||
@ApiProperty({
|
||||
example: "09226187419",
|
||||
type: "string",
|
||||
description: "User login dto",
|
||||
})
|
||||
username: string;
|
||||
|
||||
@ApiProperty({
|
||||
example: "258567",
|
||||
type: "string",
|
||||
description: "User login verify dto",
|
||||
})
|
||||
password: string;
|
||||
}
|
||||
63
src/auth/guards/actor-local.guard.ts
Normal file
63
src/auth/guards/actor-local.guard.ts
Normal file
@@ -0,0 +1,63 @@
|
||||
import {
|
||||
ExecutionContext,
|
||||
Injectable,
|
||||
UnauthorizedException,
|
||||
} from "@nestjs/common";
|
||||
import { JwtService } from "@nestjs/jwt";
|
||||
import { AuthGuard } from "@nestjs/passport";
|
||||
import { ActorAuthService } from "src/auth/auth-services/actor.auth.service";
|
||||
import { RoleEnum } from "src/Types&Enums/role.enum";
|
||||
|
||||
@Injectable()
|
||||
export class LocalActorAuthGuard extends AuthGuard("actor") {
|
||||
constructor(
|
||||
private readonly actorAuthService: ActorAuthService,
|
||||
private readonly jwtService: JwtService,
|
||||
) {
|
||||
super();
|
||||
}
|
||||
|
||||
async canActivate(context: ExecutionContext): Promise<boolean> {
|
||||
const request = context.switchToHttp().getRequest();
|
||||
const token = this.extractTokenFromHeader(request);
|
||||
const path = request.url;
|
||||
|
||||
if (!token) {
|
||||
if (path === "/actor/login") {
|
||||
const loginData = await this.actorAuthService.loginActors(request.body);
|
||||
request.user = loginData;
|
||||
request.identity = request;
|
||||
return true;
|
||||
} else {
|
||||
throw new UnauthorizedException("Token not found");
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
const payload = await this.jwtService.verifyAsync(token, {
|
||||
secret: `${process.env.SECRET}`,
|
||||
});
|
||||
|
||||
if (
|
||||
![RoleEnum.EXPERT, RoleEnum.DAMAGE_EXPERT, RoleEnum.COMPANY].includes(
|
||||
payload.role,
|
||||
)
|
||||
) {
|
||||
throw new UnauthorizedException("User role is not authorized");
|
||||
}
|
||||
|
||||
request.user = payload;
|
||||
request.identity = request.user;
|
||||
} catch {
|
||||
throw new UnauthorizedException("Invalid token");
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private extractTokenFromHeader(request: Request): string | undefined {
|
||||
//@ts-ignore
|
||||
const [type, token] = request.headers.authorization?.split(" ") ?? [];
|
||||
return type === "Bearer" ? token : undefined;
|
||||
}
|
||||
}
|
||||
127
src/auth/guards/claim-access.guard.ts
Normal file
127
src/auth/guards/claim-access.guard.ts
Normal file
@@ -0,0 +1,127 @@
|
||||
import {
|
||||
CanActivate,
|
||||
ExecutionContext,
|
||||
Injectable,
|
||||
UnauthorizedException,
|
||||
ForbiddenException,
|
||||
} from "@nestjs/common";
|
||||
import { JwtService } from "@nestjs/jwt";
|
||||
import { RoleEnum } from "src/Types&Enums/role.enum";
|
||||
import { ClaimRequestManagementDbService } from "src/claim-request-management/entites/db-service/claim-request-management.db.service";
|
||||
import { Types } from "mongoose";
|
||||
|
||||
/**
|
||||
* Guard that allows:
|
||||
* - Users to access their own claim files
|
||||
* - Experts to access IN_PERSON claim files they initiated
|
||||
*/
|
||||
@Injectable()
|
||||
export class ClaimAccessGuard implements CanActivate {
|
||||
constructor(
|
||||
private readonly jwtService: JwtService,
|
||||
private readonly claimDbService: ClaimRequestManagementDbService,
|
||||
) {}
|
||||
|
||||
async canActivate(context: ExecutionContext): Promise<boolean> {
|
||||
const request = context.switchToHttp().getRequest();
|
||||
const token = this.extractTokenFromHeader(request);
|
||||
if (!token) {
|
||||
throw new UnauthorizedException();
|
||||
}
|
||||
|
||||
try {
|
||||
const payload = await this.jwtService.verifyAsync(token, {
|
||||
secret: `${process.env.SECRET}`,
|
||||
});
|
||||
|
||||
// Allow users to pass through (they will be checked by service methods)
|
||||
if (payload.role === RoleEnum.USER) {
|
||||
request.user = payload;
|
||||
request.identity = request.user;
|
||||
return true;
|
||||
}
|
||||
|
||||
// For experts, check if they're accessing an IN_PERSON claim file they initiated
|
||||
if (
|
||||
payload.role === RoleEnum.EXPERT ||
|
||||
payload.role === RoleEnum.DAMAGE_EXPERT
|
||||
) {
|
||||
// Extract claimRequestId from route params
|
||||
const claimRequestId =
|
||||
request.params?.claimRequestId ||
|
||||
request.params?.claimRequestID ||
|
||||
request.params?.id;
|
||||
|
||||
if (!claimRequestId) {
|
||||
// If no claim ID in params, allow access (e.g., for listing endpoints)
|
||||
// The service will filter appropriately
|
||||
request.user = payload;
|
||||
request.actor = payload;
|
||||
request.identity = payload;
|
||||
return true;
|
||||
}
|
||||
|
||||
// Verify expert has access to this specific claim file
|
||||
const hasAccess = await this.verifyExpertClaimAccess(
|
||||
claimRequestId,
|
||||
payload.sub,
|
||||
);
|
||||
|
||||
if (!hasAccess) {
|
||||
throw new ForbiddenException(
|
||||
"You can only access IN_PERSON claim files that you initiated",
|
||||
);
|
||||
}
|
||||
|
||||
request.user = payload;
|
||||
request.actor = payload;
|
||||
request.identity = payload;
|
||||
return true;
|
||||
}
|
||||
|
||||
throw new UnauthorizedException("Invalid role");
|
||||
} catch (error) {
|
||||
if (error instanceof ForbiddenException || error instanceof UnauthorizedException) {
|
||||
throw error;
|
||||
}
|
||||
throw new UnauthorizedException();
|
||||
}
|
||||
}
|
||||
|
||||
private async verifyExpertClaimAccess(
|
||||
claimRequestId: string,
|
||||
expertId: string,
|
||||
): Promise<boolean> {
|
||||
try {
|
||||
const claim = await this.claimDbService.findOne(claimRequestId);
|
||||
if (!claim) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const blameFile = claim.blameFile;
|
||||
if (!blameFile) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check if it's an expert-initiated IN_PERSON file
|
||||
if (
|
||||
blameFile.expertInitiated &&
|
||||
blameFile.creationMethod === "IN_PERSON" &&
|
||||
blameFile.initiatedBy
|
||||
) {
|
||||
// Verify the expert is the one who initiated it
|
||||
return String(blameFile.initiatedBy) === expertId;
|
||||
}
|
||||
|
||||
return false;
|
||||
} catch (error) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private extractTokenFromHeader(request: any): string | undefined {
|
||||
const [type, token] = request.headers.authorization?.split(" ") ?? [];
|
||||
return type === "Bearer" ? token : undefined;
|
||||
}
|
||||
}
|
||||
|
||||
44
src/auth/guards/global.guard.ts
Normal file
44
src/auth/guards/global.guard.ts
Normal file
@@ -0,0 +1,44 @@
|
||||
import {
|
||||
CanActivate,
|
||||
ExecutionContext,
|
||||
Injectable,
|
||||
UnauthorizedException,
|
||||
} from "@nestjs/common";
|
||||
import { JwtService } from "@nestjs/jwt";
|
||||
import { Request } from "express";
|
||||
import { RoleEnum } from "src/Types&Enums/role.enum";
|
||||
|
||||
@Injectable()
|
||||
export class GlobalGuard implements CanActivate {
|
||||
constructor(private readonly jwtService: JwtService) {}
|
||||
|
||||
async canActivate(context: ExecutionContext): Promise<boolean> {
|
||||
const request = context.switchToHttp().getRequest();
|
||||
const token = this.extractTokenFromHeader(request);
|
||||
if (!token) {
|
||||
throw new UnauthorizedException();
|
||||
}
|
||||
try {
|
||||
const payload = await this.jwtService.verifyAsync(token, {
|
||||
secret: `${process.env.SECRET}`,
|
||||
});
|
||||
if (payload.role !== RoleEnum.USER) {
|
||||
console.log(
|
||||
"🚀 ~ GlobalGuard ~ canActivate ~ request.user.role:",
|
||||
request.user.role,
|
||||
);
|
||||
throw new UnauthorizedException();
|
||||
}
|
||||
request.user = payload;
|
||||
request.identity = request.user;
|
||||
} catch {
|
||||
throw new UnauthorizedException();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private extractTokenFromHeader(request: Request): string | undefined {
|
||||
const [type, token] = request.headers.authorization?.split(" ") ?? [];
|
||||
return type === "Bearer" ? token : undefined;
|
||||
}
|
||||
}
|
||||
24
src/auth/guards/role.guard.ts
Normal file
24
src/auth/guards/role.guard.ts
Normal file
@@ -0,0 +1,24 @@
|
||||
import { Injectable, CanActivate, ExecutionContext } from "@nestjs/common";
|
||||
import { Reflector } from "@nestjs/core";
|
||||
|
||||
@Injectable()
|
||||
export class RolesGuard implements CanActivate {
|
||||
constructor(private readonly reflector: Reflector) {}
|
||||
canActivate(context: ExecutionContext): boolean {
|
||||
// get the roles required
|
||||
const roles = this.reflector.getAllAndOverride<string[]>("role", [
|
||||
context.getHandler(),
|
||||
context.getClass(),
|
||||
]);
|
||||
if (!roles) {
|
||||
return false;
|
||||
}
|
||||
const request = context.switchToHttp().getRequest();
|
||||
const userRoles = request.user?.role?.split(",");
|
||||
return this.validateRoles(roles, userRoles);
|
||||
}
|
||||
|
||||
validateRoles(roles: string[], userRoles: string[]) {
|
||||
return roles.some((role) => userRoles.includes(role));
|
||||
}
|
||||
}
|
||||
27
src/auth/guards/user-local.guard.ts
Normal file
27
src/auth/guards/user-local.guard.ts
Normal file
@@ -0,0 +1,27 @@
|
||||
import {
|
||||
ExecutionContext,
|
||||
Injectable,
|
||||
NotAcceptableException,
|
||||
} from "@nestjs/common";
|
||||
import { AuthGuard } from "@nestjs/passport";
|
||||
import { UserAuthService } from "src/auth/auth-services/user.auth.service";
|
||||
|
||||
@Injectable()
|
||||
export class LocalUserAuthGuard extends AuthGuard("local") {
|
||||
constructor(private readonly userAuthService: UserAuthService) {
|
||||
super();
|
||||
}
|
||||
async canActivate(context: ExecutionContext): Promise<boolean> {
|
||||
const request = context.switchToHttp().getRequest();
|
||||
const { username, password } = request.body;
|
||||
let isValidUser = await this.userAuthService.validateUser(
|
||||
username,
|
||||
password,
|
||||
);
|
||||
if (!isValidUser) {
|
||||
throw new NotAcceptableException("otp is wrong");
|
||||
}
|
||||
request["user"] = isValidUser;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
22
src/auth/stratregys/local-actor.strategy.ts
Normal file
22
src/auth/stratregys/local-actor.strategy.ts
Normal file
@@ -0,0 +1,22 @@
|
||||
import { Injectable } from "@nestjs/common";
|
||||
import { PassportStrategy } from "@nestjs/passport";
|
||||
import { Strategy } from "passport-local";
|
||||
import { ActorAuthService } from "src/auth/auth-services/actor.auth.service";
|
||||
|
||||
@Injectable()
|
||||
export class LocalActorStrategy extends PassportStrategy(Strategy, "actor") {
|
||||
constructor(private readonly actorAuthService: ActorAuthService) {
|
||||
super();
|
||||
}
|
||||
|
||||
// async validate(username, password): Promise<any> {
|
||||
// const user = await this.actorAuthService.validateActor(
|
||||
// username,
|
||||
// password,
|
||||
// );
|
||||
// if (!user) {
|
||||
// throw new UnauthorizedException("user not found");
|
||||
// }
|
||||
// return user;
|
||||
// }
|
||||
}
|
||||
19
src/auth/stratregys/local.strategy.ts
Normal file
19
src/auth/stratregys/local.strategy.ts
Normal file
@@ -0,0 +1,19 @@
|
||||
import { Injectable, UnauthorizedException } from "@nestjs/common";
|
||||
import { PassportStrategy } from "@nestjs/passport";
|
||||
import { Strategy } from "passport-local";
|
||||
import { UserAuthService } from "src/auth/auth-services/user.auth.service";
|
||||
|
||||
@Injectable()
|
||||
export class LocalStrategy extends PassportStrategy(Strategy) {
|
||||
constructor(private readonly userAuthService: UserAuthService) {
|
||||
super();
|
||||
}
|
||||
|
||||
async validate(username: string, password: string): Promise<any> {
|
||||
const user = await this.userAuthService.validateUser(username, password);
|
||||
if (!user) {
|
||||
throw new UnauthorizedException("user not found please register");
|
||||
}
|
||||
return user;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,483 @@
|
||||
import { readFile } from "node:fs/promises";
|
||||
import { extname, parse } from "node:path";
|
||||
import {
|
||||
BadRequestException,
|
||||
Body,
|
||||
Controller,
|
||||
Get,
|
||||
Param,
|
||||
Patch,
|
||||
Post,
|
||||
Put,
|
||||
Query,
|
||||
UploadedFile,
|
||||
UseGuards,
|
||||
UseInterceptors,
|
||||
} from "@nestjs/common";
|
||||
import { FileInterceptor } from "@nestjs/platform-express";
|
||||
import {
|
||||
ApiBearerAuth,
|
||||
ApiBody,
|
||||
ApiConsumes,
|
||||
ApiParam,
|
||||
ApiQuery,
|
||||
ApiTags,
|
||||
} from "@nestjs/swagger";
|
||||
import { diskStorage } from "multer";
|
||||
import { GlobalGuard } from "src/auth/guards/global.guard";
|
||||
import { ClaimAccessGuard } from "src/auth/guards/claim-access.guard";
|
||||
import { RolesGuard } from "src/auth/guards/role.guard";
|
||||
import { Roles } from "src/decorators/roles.decorator";
|
||||
import { CurrentUser } from "src/decorators/user.decorator";
|
||||
import { RoleEnum } from "src/Types&Enums/role.enum";
|
||||
import { ClaimRequestManagementService } from "./claim-request-management.service";
|
||||
import { CarDamagePartDto, OtherCarDamagePartDto } from "./dto/car-part.dto";
|
||||
import { UserCommentDto } from "./dto/user-comment.dto";
|
||||
import { UserObjectionDto } from "./dto/user-objection.dto";
|
||||
import { InPersonVisitDto } from "./dto/in-person-visit.dto";
|
||||
|
||||
@Controller("claim-request-management")
|
||||
@ApiTags("claim-request-management")
|
||||
@Roles(RoleEnum.USER, RoleEnum.EXPERT, RoleEnum.DAMAGE_EXPERT)
|
||||
@UseGuards(ClaimAccessGuard, RolesGuard)
|
||||
@ApiBearerAuth()
|
||||
export class ClaimRequestManagementController {
|
||||
constructor(
|
||||
private readonly claimRequestManagementService: ClaimRequestManagementService,
|
||||
) {}
|
||||
|
||||
@ApiParam({ name: "blameId" })
|
||||
@Post("/:blameId")
|
||||
async createClaimRequest(
|
||||
@Param("blameId") requestId: string,
|
||||
@CurrentUser() user,
|
||||
) {
|
||||
return await this.claimRequestManagementService.createClaimRequest(
|
||||
requestId,
|
||||
user.role === RoleEnum.USER ? user.sub : undefined,
|
||||
user,
|
||||
);
|
||||
}
|
||||
|
||||
@ApiBody({ type: CarDamagePartDto })
|
||||
@Patch("/car-part-damage/:claimRequestID")
|
||||
@ApiParam({ name: "claimRequestID" })
|
||||
async carPartDamage(
|
||||
@Param("claimRequestID") requestId: string,
|
||||
@Body() body: CarDamagePartDto,
|
||||
@CurrentUser() user,
|
||||
) {
|
||||
return await this.claimRequestManagementService.selectCarPartDamage(
|
||||
requestId,
|
||||
body,
|
||||
user,
|
||||
);
|
||||
}
|
||||
|
||||
@Get("/car-other-part")
|
||||
async getCarOtherParts() {
|
||||
const carOtherPart = await readFile(
|
||||
`${process.cwd()}/src/static/car-part.json`,
|
||||
"utf-8",
|
||||
);
|
||||
return carOtherPart;
|
||||
}
|
||||
|
||||
@ApiBody({ type: OtherCarDamagePartDto })
|
||||
@ApiParam({ name: "claimRequestID" })
|
||||
@UseInterceptors(
|
||||
FileInterceptor("file", {
|
||||
limits: {
|
||||
fileSize: 10 * 1024 * 1024,
|
||||
},
|
||||
storage: diskStorage({
|
||||
destination: "./files/car-green-cards",
|
||||
filename: (req, file, callback) => {
|
||||
const unique = Date.now();
|
||||
const ex = extname(file.originalname);
|
||||
const filename = `${file.originalname.split(" ")[0]}-${unique}${ex}`;
|
||||
callback(null, filename);
|
||||
},
|
||||
}),
|
||||
}),
|
||||
)
|
||||
@ApiConsumes("multipart/form-data")
|
||||
@Patch("/car-other-part-damage/:claimRequestID")
|
||||
async carOtherPartDamage(
|
||||
@Param("claimRequestID") requestId: string,
|
||||
@UploadedFile() file,
|
||||
@Body() body: OtherCarDamagePartDto,
|
||||
@CurrentUser() user,
|
||||
) {
|
||||
return await this.claimRequestManagementService.selectCarOtherPartDamage(
|
||||
requestId,
|
||||
body,
|
||||
file,
|
||||
user,
|
||||
);
|
||||
}
|
||||
|
||||
@Get("required-documents-status/:claimRequestID")
|
||||
@ApiParam({ name: "claimRequestID" })
|
||||
async getRequiredDocumentsStatus(
|
||||
@Param("claimRequestID") requestId: string,
|
||||
) {
|
||||
return await this.claimRequestManagementService.getRequiredDocumentsStatus(
|
||||
requestId,
|
||||
);
|
||||
}
|
||||
|
||||
@Get("car-part-image-required/:claimRequestID")
|
||||
@ApiParam({ name: "claimRequestID" })
|
||||
async getImageRequired(@Param("claimRequestID") requestId) {
|
||||
return await this.claimRequestManagementService.getImageRequiredList(
|
||||
requestId,
|
||||
);
|
||||
}
|
||||
|
||||
@ApiBody({
|
||||
schema: {
|
||||
type: "object",
|
||||
properties: {
|
||||
file: { type: "string", format: "binary" },
|
||||
},
|
||||
},
|
||||
})
|
||||
@UseInterceptors(
|
||||
FileInterceptor("file", {
|
||||
limits: { fileSize: 10 * 1024 * 1024 },
|
||||
storage: diskStorage({
|
||||
destination: "./files/claim-required-documents/",
|
||||
filename: (req, file, callback) => {
|
||||
const extension = extname(file.originalname);
|
||||
const basename = parse(file.originalname).name;
|
||||
const unique = Date.now();
|
||||
const sanitizedBasename = basename
|
||||
.replace(/\s/g, "_")
|
||||
.replace(/[^\w\-_]/g, "")
|
||||
.substring(0, 50);
|
||||
const filename = `${sanitizedBasename}-${unique}${extension}`;
|
||||
callback(null, filename);
|
||||
},
|
||||
}),
|
||||
}),
|
||||
)
|
||||
@ApiConsumes("multipart/form-data")
|
||||
@ApiParam({ name: "claimRequestID" })
|
||||
@ApiQuery({
|
||||
name: "documentType",
|
||||
enum: [
|
||||
"damaged_driving_license_back",
|
||||
"damaged_driving_license_front",
|
||||
"damaged_chassis_number",
|
||||
"damaged_engine_photo",
|
||||
"damaged_car_card_front",
|
||||
"damaged_car_card_back",
|
||||
"damaged_metal_plate",
|
||||
"guilty_driving_license_front",
|
||||
"guilty_driving_license_back",
|
||||
"guilty_car_card_front",
|
||||
"guilty_car_card_back",
|
||||
"guilty_metal_plate",
|
||||
],
|
||||
description: "Type of required document to upload",
|
||||
})
|
||||
@Patch("upload-required-document/:claimRequestID")
|
||||
async uploadRequiredDocument(
|
||||
@Param("claimRequestID") requestId: string,
|
||||
@Query("documentType") documentType: string,
|
||||
@UploadedFile("file") file: Express.Multer.File,
|
||||
@CurrentUser() user,
|
||||
) {
|
||||
if (!file) {
|
||||
throw new BadRequestException("File is required");
|
||||
}
|
||||
return await this.claimRequestManagementService.uploadRequiredDocument(
|
||||
requestId,
|
||||
documentType as any,
|
||||
file,
|
||||
user,
|
||||
);
|
||||
}
|
||||
|
||||
@ApiBody({
|
||||
schema: {
|
||||
type: "object",
|
||||
properties: {
|
||||
file: { type: "string", format: "binary" },
|
||||
},
|
||||
},
|
||||
})
|
||||
@UseInterceptors(
|
||||
FileInterceptor("file", {
|
||||
limits: { fileSize: 10 * 1024 * 1024 },
|
||||
storage: diskStorage({
|
||||
destination: "./files/car-parts/",
|
||||
filename: (req, file, callback) => {
|
||||
const extension = extname(file.originalname);
|
||||
const basename = parse(file.originalname).name;
|
||||
const unique = Date.now();
|
||||
// Sanitize filename: remove non-ASCII characters and special chars to avoid encoding issues
|
||||
const sanitizedBasename = basename
|
||||
.replace(/\s/g, "_")
|
||||
.replace(/[^\w\-_]/g, "") // Remove all non-word characters except hyphens and underscores
|
||||
.substring(0, 50); // Limit length
|
||||
const filename = `${sanitizedBasename}-${unique}${extension}`;
|
||||
callback(null, filename);
|
||||
},
|
||||
}),
|
||||
}),
|
||||
)
|
||||
@ApiConsumes("multipart/form-data")
|
||||
@ApiParam({ name: "claimRequestID" })
|
||||
@ApiParam({
|
||||
name: "partId",
|
||||
description: "The ID of the specific car part being photographed.",
|
||||
})
|
||||
@Patch("capture-car-part-damage/:claimRequestID/:partId")
|
||||
async captureCarPartDamage(
|
||||
@Param("partId") partId: string,
|
||||
@Param("claimRequestID") requestId: string,
|
||||
@UploadedFile("file") file: Express.Multer.File,
|
||||
) {
|
||||
if (!file) {
|
||||
throw new BadRequestException("Image file is required.");
|
||||
}
|
||||
return await this.claimRequestManagementService.setDamageImage(
|
||||
requestId,
|
||||
partId,
|
||||
file,
|
||||
);
|
||||
}
|
||||
|
||||
@ApiBody({
|
||||
schema: {
|
||||
type: "object",
|
||||
properties: {
|
||||
file: { type: "string", format: "binary" },
|
||||
},
|
||||
},
|
||||
})
|
||||
@UseInterceptors(
|
||||
FileInterceptor("file", {
|
||||
limits: { fileSize: 50 * 1024 * 1024 },
|
||||
storage: diskStorage({
|
||||
destination: "./files/car-capture-videos/",
|
||||
filename: (req, file, callback) => {
|
||||
const unique = Date.now();
|
||||
const ex = extname(file.originalname);
|
||||
const filename = `claim-video-${unique}${ex}`;
|
||||
callback(null, filename);
|
||||
},
|
||||
}),
|
||||
}),
|
||||
)
|
||||
@ApiConsumes("multipart/form-data")
|
||||
@ApiParam({ name: "claimRequestID" })
|
||||
@Patch("car-capture/:claimRequestID")
|
||||
async captureVideoCapture(
|
||||
@Param("claimRequestID") requestId: string,
|
||||
@UploadedFile("file") file: Express.Multer.File,
|
||||
) {
|
||||
return await this.claimRequestManagementService.setVideoCapture(
|
||||
requestId,
|
||||
file,
|
||||
);
|
||||
}
|
||||
|
||||
@Get("requests/")
|
||||
async getRequest(@CurrentUser() currentUser) {
|
||||
return await this.claimRequestManagementService.myRequests(currentUser);
|
||||
}
|
||||
|
||||
@Get("request/:claimRequestId")
|
||||
@ApiParam({ name: "claimRequestId" })
|
||||
myRequests(
|
||||
@Param("claimRequestId") requestId: string,
|
||||
@CurrentUser() user,
|
||||
) {
|
||||
return this.claimRequestManagementService.requestDetails(requestId, user);
|
||||
}
|
||||
|
||||
@Put("request/reply/:claimRequestId")
|
||||
@ApiParam({ name: "claimRequestId" })
|
||||
@UseInterceptors(
|
||||
FileInterceptor("file", {
|
||||
limits: {
|
||||
fileSize: 10 * 1024 * 1024,
|
||||
},
|
||||
storage: diskStorage({
|
||||
destination: "./files/claim-sign",
|
||||
filename: (req, file, callback) => {
|
||||
const unique = Date.now();
|
||||
const ex = extname(file.originalname);
|
||||
const filename = `${file.originalname.split(" ")[0]}-${unique}${ex}`;
|
||||
callback(null, filename);
|
||||
},
|
||||
}),
|
||||
}),
|
||||
)
|
||||
@ApiBody({
|
||||
type: UserCommentDto,
|
||||
description: "if partId null , you can upload video capture",
|
||||
})
|
||||
@ApiConsumes("multipart/form-data")
|
||||
@ApiParam({ name: "claimRequestId" })
|
||||
async submitReply(
|
||||
@Param("claimRequestId") requestId,
|
||||
@Body() body,
|
||||
@UploadedFile() file: Express.Multer.File,
|
||||
@CurrentUser() user,
|
||||
) {
|
||||
return await this.claimRequestManagementService.submitUserReply(
|
||||
requestId,
|
||||
body,
|
||||
file,
|
||||
user,
|
||||
);
|
||||
}
|
||||
|
||||
@Put("request/resend/:claimRequestId/objection")
|
||||
@ApiParam({ name: "claimRequestId" })
|
||||
@ApiConsumes("application/json")
|
||||
@ApiBody({
|
||||
type: UserObjectionDto,
|
||||
description: "Objection details with optional new parts",
|
||||
})
|
||||
async handleUserObjection(
|
||||
@Param("claimRequestId") claimRequestId: string,
|
||||
@Body() userObjectionDto: UserObjectionDto,
|
||||
) {
|
||||
return await this.claimRequestManagementService.handleUserObjectionAndParts(
|
||||
claimRequestId,
|
||||
userObjectionDto,
|
||||
);
|
||||
}
|
||||
|
||||
@Patch("request/resend/:claimRequestId")
|
||||
@ApiConsumes("multipart/form-data")
|
||||
@ApiParam({ name: "claimRequestId" })
|
||||
@ApiQuery({ name: "fields", enum: ["resendDocuments", "resendCarParts"] })
|
||||
@ApiQuery({ name: "partId", required: false })
|
||||
@ApiQuery({ name: "documentName", required: false })
|
||||
@ApiQuery({ name: "side", required: false })
|
||||
@ApiBody({
|
||||
schema: {
|
||||
type: "object",
|
||||
properties: {
|
||||
file: {
|
||||
type: "string",
|
||||
format: "binary",
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
@UseInterceptors(
|
||||
FileInterceptor("file", {
|
||||
storage: diskStorage({
|
||||
destination: "./files/claim-resend-documents",
|
||||
filename: (req, file, callback) => {
|
||||
const unique = Date.now();
|
||||
const ext = extname(file.originalname);
|
||||
const filename = `${file.originalname.split(" ")[0]}-${unique}${ext}`;
|
||||
callback(null, filename);
|
||||
},
|
||||
}),
|
||||
limits: { fileSize: 10 * 1024 * 1024 },
|
||||
}),
|
||||
)
|
||||
async uploadDocuments(
|
||||
@Param("claimRequestId") claimId: string,
|
||||
@Query() query: string,
|
||||
@UploadedFile() file: Express.Multer.File,
|
||||
@CurrentUser() user,
|
||||
) {
|
||||
return await this.claimRequestManagementService.resendFiles(
|
||||
claimId,
|
||||
file,
|
||||
query,
|
||||
user,
|
||||
);
|
||||
}
|
||||
|
||||
@Patch("request/reply/:claimRequestId/:partId/upload-factor")
|
||||
@ApiConsumes("multipart/form-data")
|
||||
@ApiParam({ name: "claimRequestId" })
|
||||
@ApiParam({ name: "partId" })
|
||||
@ApiBody({
|
||||
schema: {
|
||||
type: "object",
|
||||
properties: {
|
||||
file: {
|
||||
type: "string",
|
||||
format: "binary",
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
@UseInterceptors(
|
||||
FileInterceptor("file", {
|
||||
storage: diskStorage({
|
||||
destination: "./files/claim-factors",
|
||||
filename: (req, file, callback) => {
|
||||
const unique = Date.now();
|
||||
const filename = `-${unique}-${file.originalname}`;
|
||||
callback(null, filename);
|
||||
},
|
||||
}),
|
||||
limits: { fileSize: 10 * 1024 * 1024 },
|
||||
}),
|
||||
)
|
||||
async uploadFactorForPart(
|
||||
@Param("claimRequestId") claimId: string,
|
||||
@Param("partId") partId: string,
|
||||
@UploadedFile() file: Express.Multer.File,
|
||||
@CurrentUser() user,
|
||||
) {
|
||||
return await this.claimRequestManagementService.uploadClaimFactor(
|
||||
claimId,
|
||||
partId,
|
||||
file,
|
||||
user,
|
||||
);
|
||||
}
|
||||
|
||||
@ApiBody({ type: InPersonVisitDto })
|
||||
@ApiParam({ name: "id" })
|
||||
@Patch(":id/visit")
|
||||
async inPersonVisit(
|
||||
@Param("id") requestId: string,
|
||||
@Body() body: InPersonVisitDto,
|
||||
@CurrentUser() actor,
|
||||
) {
|
||||
// Pass the branchId from the body to the service
|
||||
return await this.claimRequestManagementService.inPersonVisit(
|
||||
requestId,
|
||||
body.branchId,
|
||||
actor,
|
||||
);
|
||||
}
|
||||
|
||||
@Get("branches/:insuranceId")
|
||||
async insuranceBranches(@Param("insuranceId") insuranceId: string) {
|
||||
return await this.claimRequestManagementService.retrieveInsuranceBranches(
|
||||
insuranceId,
|
||||
);
|
||||
}
|
||||
|
||||
@Get("fanavaran-submit/:claimRequestId")
|
||||
@ApiParam({ name: "claimRequestId" })
|
||||
async fanavaranSubmit(@Param("claimRequestId") claimRequestId: string) {
|
||||
return await this.claimRequestManagementService.fanavaranSubmit(
|
||||
claimRequestId,
|
||||
);
|
||||
}
|
||||
|
||||
@Post("fanavaran-submit/:claimRequestId")
|
||||
@ApiParam({ name: "claimRequestId" })
|
||||
async submitToFanavaran(@Param("claimRequestId") claimRequestId: string) {
|
||||
return await this.claimRequestManagementService.submitToFanavaran(
|
||||
claimRequestId,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
import { Module } from "@nestjs/common";
|
||||
import { MongooseModule } from "@nestjs/mongoose";
|
||||
import { AiModule } from "src/ai/ai.module";
|
||||
import { SandHubModule } from "src/sand-hub/sand-hub.module";
|
||||
import { RequestManagementModule } from "src/request-management/request-management.module";
|
||||
import { UsersModule } from "src/users/users.module";
|
||||
import { ClaimRequestManagementController } from "./claim-request-management.controller";
|
||||
import { ClaimRequestManagementService } from "./claim-request-management.service";
|
||||
import { CarGreenCardDbService } from "./entites/db-service/car-green-card.db.service";
|
||||
import { ClaimRequestManagementDbService } from "./entites/db-service/claim-request-management.db.service";
|
||||
import { ClaimSignDbService } from "./entites/db-service/claim-sign.db.service";
|
||||
import { DamageImageDbService } from "./entites/db-service/damage-image.db.service";
|
||||
import { ClaimFactorsImageDbService } from "./entites/db-service/factor-image.db.service";
|
||||
import { VideoCaptureDbService } from "./entites/db-service/video-capture.db.service";
|
||||
import { ClaimRequiredDocumentDbService } from "./entites/db-service/claim-required-document.db.service";
|
||||
import {
|
||||
CarGreenCardModel,
|
||||
CarGreenCardSchema,
|
||||
} from "./entites/schema/car-green-card.schema";
|
||||
import {
|
||||
ClaimRequiredDocument,
|
||||
ClaimRequiredDocumentSchema,
|
||||
} from "./entites/schema/claim-required-document.schema";
|
||||
import {
|
||||
ClaimRequestManagementModel,
|
||||
ClaimRequestManagementSchema,
|
||||
} from "./entites/schema/claim-request-management.schema";
|
||||
import { ClaimSignModel, ClaimSignSchema } from "./entites/schema/claim-sign";
|
||||
import {
|
||||
DamageImageModelSchema,
|
||||
DamagePartImageModel,
|
||||
} from "./entites/schema/damage-image-part.schema";
|
||||
import {
|
||||
ClaimFactorsImage,
|
||||
ClaimFactorsImageSchema,
|
||||
} from "./entites/schema/factor-image.schema";
|
||||
import {
|
||||
VideoCaptureModel,
|
||||
VideoCaptureSchema,
|
||||
} from "./entites/schema/video-capture.schema";
|
||||
import { ClientModule } from "src/client/client.module";
|
||||
import { ClaimAccessGuard } from "src/auth/guards/claim-access.guard";
|
||||
import { JwtModule } from "@nestjs/jwt";
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
UsersModule,
|
||||
RequestManagementModule,
|
||||
AiModule,
|
||||
SandHubModule,
|
||||
ClientModule,
|
||||
JwtModule.register({}),
|
||||
MongooseModule.forFeature([
|
||||
{
|
||||
name: ClaimRequestManagementModel.name,
|
||||
schema: ClaimRequestManagementSchema,
|
||||
},
|
||||
{ name: CarGreenCardModel.name, schema: CarGreenCardSchema },
|
||||
{ name: DamagePartImageModel.name, schema: DamageImageModelSchema },
|
||||
{ name: ClaimSignModel.name, schema: ClaimSignSchema },
|
||||
{ name: ClaimFactorsImage.name, schema: ClaimFactorsImageSchema },
|
||||
{ name: VideoCaptureModel.name, schema: VideoCaptureSchema },
|
||||
{
|
||||
name: ClaimRequiredDocument.name,
|
||||
schema: ClaimRequiredDocumentSchema,
|
||||
},
|
||||
]),
|
||||
],
|
||||
providers: [
|
||||
ClaimRequestManagementService,
|
||||
ClaimRequestManagementDbService,
|
||||
CarGreenCardDbService,
|
||||
DamageImageDbService,
|
||||
ClaimSignDbService,
|
||||
ClaimFactorsImageDbService,
|
||||
VideoCaptureDbService,
|
||||
ClaimRequiredDocumentDbService,
|
||||
ClaimAccessGuard,
|
||||
],
|
||||
controllers: [ClaimRequestManagementController],
|
||||
exports: [
|
||||
ClaimRequestManagementDbService,
|
||||
DamageImageDbService,
|
||||
VideoCaptureDbService,
|
||||
ClaimRequiredDocumentDbService,
|
||||
],
|
||||
})
|
||||
export class ClaimRequestManagementModule {}
|
||||
2556
src/claim-request-management/claim-request-management.service.ts
Normal file
2556
src/claim-request-management/claim-request-management.service.ts
Normal file
File diff suppressed because it is too large
Load Diff
108
src/claim-request-management/dto/car-part.dto.ts
Normal file
108
src/claim-request-management/dto/car-part.dto.ts
Normal file
@@ -0,0 +1,108 @@
|
||||
import { ApiProperty } from "@nestjs/swagger";
|
||||
|
||||
export class MainParts {
|
||||
@ApiProperty({ default: false })
|
||||
backFender: boolean;
|
||||
|
||||
@ApiProperty({ default: false })
|
||||
backWheel: boolean;
|
||||
|
||||
@ApiProperty({ default: false })
|
||||
backDoor: boolean;
|
||||
|
||||
@ApiProperty({ default: false })
|
||||
frontDoor: boolean;
|
||||
|
||||
@ApiProperty({ default: false })
|
||||
mirror: boolean;
|
||||
|
||||
@ApiProperty({ default: false })
|
||||
frontWheel: boolean;
|
||||
|
||||
@ApiProperty({ default: false })
|
||||
frontFender: boolean;
|
||||
|
||||
@ApiProperty({ default: false })
|
||||
backWindow: boolean;
|
||||
|
||||
@ApiProperty({ default: false })
|
||||
frontWindow: boolean;
|
||||
}
|
||||
export class FrontParts {
|
||||
@ApiProperty({ default: false })
|
||||
frontBumper: boolean;
|
||||
|
||||
@ApiProperty({ default: false })
|
||||
frontCarWindshield: boolean;
|
||||
|
||||
@ApiProperty({ default: false })
|
||||
carHood: boolean;
|
||||
|
||||
@ApiProperty({ default: false })
|
||||
leftLight: boolean;
|
||||
|
||||
@ApiProperty({ default: false })
|
||||
rightLight: boolean;
|
||||
|
||||
@ApiProperty({ default: false, description: "جلو پنجره " })
|
||||
frontGrille: boolean;
|
||||
}
|
||||
export class BackParts {
|
||||
@ApiProperty({ default: false })
|
||||
backBumper: boolean;
|
||||
|
||||
@ApiProperty({ default: false })
|
||||
carTrunk: boolean;
|
||||
|
||||
@ApiProperty({ default: false })
|
||||
backCarWindshield: boolean;
|
||||
|
||||
@ApiProperty({ default: false })
|
||||
leftLight: boolean;
|
||||
|
||||
@ApiProperty({ default: false })
|
||||
rightLight: boolean;
|
||||
}
|
||||
export class TopParts {
|
||||
@ApiProperty({ default: false })
|
||||
roof: boolean;
|
||||
}
|
||||
export class CarDamagePartDto {
|
||||
@ApiProperty({ type: MainParts })
|
||||
left: MainParts[];
|
||||
|
||||
@ApiProperty({ type: MainParts })
|
||||
right: MainParts[];
|
||||
|
||||
@ApiProperty({ type: FrontParts })
|
||||
front: FrontParts[];
|
||||
|
||||
@ApiProperty({ type: BackParts })
|
||||
back: BackParts[];
|
||||
|
||||
@ApiProperty({ type: TopParts })
|
||||
top: TopParts[];
|
||||
}
|
||||
|
||||
export class OtherCarDamagePartDto {
|
||||
@ApiProperty({
|
||||
format: "array",
|
||||
description: "please add items of json into array",
|
||||
example: [{ "حسگر درها": true }],
|
||||
})
|
||||
otherParts: [];
|
||||
|
||||
@ApiProperty({ format: "string" })
|
||||
sheba: string;
|
||||
|
||||
@ApiProperty({ format: "string" })
|
||||
nationalCodeOfInsurer: string;
|
||||
|
||||
@ApiProperty({ type: "string", format: "binary", required: true })
|
||||
file: Express.Multer.File;
|
||||
}
|
||||
|
||||
export class CaptureCarPartDto {
|
||||
@ApiProperty({ type: "string", format: "binary", required: true })
|
||||
file: Express.Multer.File;
|
||||
}
|
||||
16
src/claim-request-management/dto/claim-detail.ts
Normal file
16
src/claim-request-management/dto/claim-detail.ts
Normal file
@@ -0,0 +1,16 @@
|
||||
import { ClaimRequestManagementModel } from "src/claim-request-management/entites/schema/claim-request-management.schema";
|
||||
|
||||
export class ClaimPartUploadDetail {
|
||||
list: any;
|
||||
constructor(claimFile: ClaimRequestManagementModel[]) {
|
||||
this.list = claimFile
|
||||
.map((c) => {
|
||||
return {
|
||||
carPartDamage: c.carPartDamage,
|
||||
carOtherPartDamage: c.otherParts,
|
||||
greenCardUpload: !!c.carGreenCard.path,
|
||||
};
|
||||
})
|
||||
.flat(2);
|
||||
}
|
||||
}
|
||||
17
src/claim-request-management/dto/claim-rs-dto.ts
Normal file
17
src/claim-request-management/dto/claim-rs-dto.ts
Normal file
@@ -0,0 +1,17 @@
|
||||
import { ClaimRequestManagementModel } from "src/claim-request-management/entites/schema/claim-request-management.schema";
|
||||
import { ReqClaimStatus } from "src/Types&Enums/claim-request-management/status.enum";
|
||||
|
||||
export class ClaimRequestDtoRs {
|
||||
requestDetail: any;
|
||||
messsage: string;
|
||||
status: ReqClaimStatus;
|
||||
constructor(
|
||||
req: ClaimRequestManagementModel,
|
||||
message: string,
|
||||
status: ReqClaimStatus,
|
||||
) {
|
||||
this.requestDetail = req;
|
||||
this.messsage = message;
|
||||
this.status = status;
|
||||
}
|
||||
}
|
||||
8
src/claim-request-management/dto/create-claim.dto.ts
Normal file
8
src/claim-request-management/dto/create-claim.dto.ts
Normal file
@@ -0,0 +1,8 @@
|
||||
export class CreateClaimRequestDtoRs {
|
||||
message: string;
|
||||
requestId: string;
|
||||
constructor(requestId: string, message: string) {
|
||||
this.requestId = requestId;
|
||||
this.message = message;
|
||||
}
|
||||
}
|
||||
8
src/claim-request-management/dto/image-required.dto.ts
Normal file
8
src/claim-request-management/dto/image-required.dto.ts
Normal file
@@ -0,0 +1,8 @@
|
||||
import { ClaimRequestManagementModel } from "src/claim-request-management/entites/schema/claim-request-management.schema";
|
||||
|
||||
export class ImageRequiredDto {
|
||||
public list: {} = {};
|
||||
constructor(imageModel: ClaimRequestManagementModel) {
|
||||
this.list = imageModel.imageRequired;
|
||||
}
|
||||
}
|
||||
12
src/claim-request-management/dto/in-person-visit.dto.ts
Normal file
12
src/claim-request-management/dto/in-person-visit.dto.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
import { ApiProperty } from "@nestjs/swagger";
|
||||
import { IsMongoId, IsNotEmpty } from "class-validator";
|
||||
|
||||
export class InPersonVisitDto {
|
||||
@ApiProperty({
|
||||
example: "60d5ec49e7b2f8001c8e4d2a",
|
||||
description: "The unique ID of the branch the user is being sent to.",
|
||||
})
|
||||
@IsNotEmpty()
|
||||
@IsMongoId()
|
||||
branchId: string;
|
||||
}
|
||||
23
src/claim-request-management/dto/my-request.dto.ts
Normal file
23
src/claim-request-management/dto/my-request.dto.ts
Normal file
@@ -0,0 +1,23 @@
|
||||
import { Types } from "mongoose";
|
||||
import { ClaimRequestManagementModel } from "src/claim-request-management/entites/schema/claim-request-management.schema";
|
||||
import { ReqClaimStatus } from "src/Types&Enums/claim-request-management/status.enum";
|
||||
|
||||
export class MyRequestsDtoList {
|
||||
status: ReqClaimStatus;
|
||||
submitDate: string;
|
||||
numberOfRequest: number;
|
||||
_id: Types.ObjectId;
|
||||
constructor(request: ClaimRequestManagementModel) {
|
||||
this._id = request["_id"];
|
||||
this.status = request.claimStatus;
|
||||
this.submitDate = new Date(request.createdAt).toLocaleString("fa-IR");
|
||||
this.numberOfRequest = request.requestNumber;
|
||||
}
|
||||
}
|
||||
|
||||
export class MyRequestsDto {
|
||||
public list = [];
|
||||
constructor(requests: ClaimRequestManagementModel[]) {
|
||||
this.list = requests.map((r) => new MyRequestsDtoList(r));
|
||||
}
|
||||
}
|
||||
19
src/claim-request-management/dto/submit-reply.dto.ts
Normal file
19
src/claim-request-management/dto/submit-reply.dto.ts
Normal file
@@ -0,0 +1,19 @@
|
||||
import { ClaimRequestManagementModel } from "src/claim-request-management/entites/schema/claim-request-management.schema";
|
||||
import { UserCommentDto } from "src/claim-request-management/dto/user-comment.dto";
|
||||
|
||||
export class SubmitUserReplyDtoRs {
|
||||
requestId: string;
|
||||
partsNeedFactorDetail: object;
|
||||
partsNeedFactor: boolean;
|
||||
userComment: UserCommentDto;
|
||||
constructor(
|
||||
claim: ClaimRequestManagementModel,
|
||||
partsFactorDetail?,
|
||||
partsNeedFactor?,
|
||||
) {
|
||||
this.requestId = claim["_id"];
|
||||
this.partsNeedFactorDetail = partsFactorDetail;
|
||||
this.partsNeedFactor = partsNeedFactor;
|
||||
this.userComment = claim.damageExpertReply.userComment;
|
||||
}
|
||||
}
|
||||
12
src/claim-request-management/dto/user-comment.dto.ts
Normal file
12
src/claim-request-management/dto/user-comment.dto.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
import { ApiProperty } from "@nestjs/swagger";
|
||||
|
||||
export class UserCommentDto {
|
||||
@ApiProperty({ type: Boolean })
|
||||
isAccept: boolean;
|
||||
|
||||
@ApiProperty({ type: "string", format: "binary", required: false })
|
||||
file?: Express.Multer.File;
|
||||
|
||||
@ApiProperty({ type: String })
|
||||
branch?: string;
|
||||
}
|
||||
47
src/claim-request-management/dto/user-objection.dto.ts
Normal file
47
src/claim-request-management/dto/user-objection.dto.ts
Normal file
@@ -0,0 +1,47 @@
|
||||
import { ApiProperty } from "@nestjs/swagger";
|
||||
import { Type } from "class-transformer";
|
||||
import { TypeOfDamage } from "src/Types&Enums/claim-request-management/type-of-damage.enum";
|
||||
|
||||
export class UserObjectionPartDto {
|
||||
@ApiProperty()
|
||||
partId: string;
|
||||
|
||||
@ApiProperty({ required: false })
|
||||
reason?: string;
|
||||
|
||||
@ApiProperty({ required: false })
|
||||
partPrice?: string;
|
||||
|
||||
@ApiProperty({ required: false })
|
||||
partSalary?: string;
|
||||
|
||||
@ApiProperty({ required: false })
|
||||
typeOfDamage?: TypeOfDamage;
|
||||
|
||||
@ApiProperty({ required: false })
|
||||
carPartDamage?: string;
|
||||
|
||||
@ApiProperty({ required: false })
|
||||
side?: string;
|
||||
}
|
||||
|
||||
export class NewPartDto {
|
||||
@ApiProperty({ required: false, nullable: true })
|
||||
partId: string | null;
|
||||
|
||||
@ApiProperty()
|
||||
partName: string;
|
||||
|
||||
@ApiProperty({ required: false })
|
||||
side?: string;
|
||||
}
|
||||
|
||||
export class UserObjectionDto {
|
||||
@ApiProperty({ type: [UserObjectionPartDto], required: false })
|
||||
@Type(() => UserObjectionPartDto)
|
||||
objectionParts?: UserObjectionPartDto[];
|
||||
|
||||
@ApiProperty({ type: [NewPartDto], required: false })
|
||||
@Type(() => NewPartDto)
|
||||
newParts?: NewPartDto[];
|
||||
}
|
||||
4
src/claim-request-management/dto/user-reply.dto.ts
Normal file
4
src/claim-request-management/dto/user-reply.dto.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
export class UserReplyDtoRs {
|
||||
requestId: string;
|
||||
isAccept: string;
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
import { InjectModel } from "@nestjs/mongoose";
|
||||
import { FilterQuery, Model } from "mongoose";
|
||||
import { CarGreenCardModel } from "src/claim-request-management/entites/schema/car-green-card.schema";
|
||||
|
||||
export class CarGreenCardDbService {
|
||||
constructor(
|
||||
@InjectModel(CarGreenCardModel.name)
|
||||
private readonly model: Model<CarGreenCardModel>,
|
||||
) {}
|
||||
async create(greenCard): Promise<CarGreenCardModel> {
|
||||
return await this.model.create(greenCard);
|
||||
}
|
||||
|
||||
async findOne(
|
||||
filter: FilterQuery<CarGreenCardModel>,
|
||||
): Promise<CarGreenCardModel> {
|
||||
return await this.model.findOne({ filter });
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,121 @@
|
||||
import { Injectable } from "@nestjs/common";
|
||||
import { InjectModel } from "@nestjs/mongoose";
|
||||
import { FilterQuery, Model, Types, UpdateQuery } from "mongoose";
|
||||
import { ClaimRequestManagementModel } from "src/claim-request-management/entites/schema/claim-request-management.schema";
|
||||
const crypto = require("node:crypto");
|
||||
|
||||
@Injectable()
|
||||
export class ClaimRequestManagementDbService {
|
||||
constructor(
|
||||
@InjectModel(ClaimRequestManagementModel.name)
|
||||
private readonly model: Model<ClaimRequestManagementModel>,
|
||||
) {}
|
||||
|
||||
async create(
|
||||
claimRequest: ClaimRequestManagementModel,
|
||||
): Promise<ClaimRequestManagementModel> {
|
||||
const uniqueRequestNumber = await this.generateUniqueNumbers();
|
||||
return this.model.create({
|
||||
...claimRequest,
|
||||
requestNumber: uniqueRequestNumber,
|
||||
});
|
||||
}
|
||||
|
||||
async findOne(
|
||||
id?: string,
|
||||
filter?: FilterQuery<ClaimRequestManagementModel>,
|
||||
) {
|
||||
if (filter) return await this.model.findOne(filter);
|
||||
return await this.model.findOne({ _id: new Types.ObjectId(id) });
|
||||
}
|
||||
|
||||
async findOneDocument(
|
||||
id: string,
|
||||
filter?: FilterQuery<ClaimRequestManagementModel>,
|
||||
) {
|
||||
if (filter) return await this.model.findOne(filter);
|
||||
return await this.model.findOne({ _id: new Types.ObjectId(id) }).lean();
|
||||
}
|
||||
|
||||
async findOneAndUpdate(
|
||||
filter: FilterQuery<ClaimRequestManagementModel>,
|
||||
update: UpdateQuery<ClaimRequestManagementModel>,
|
||||
option?,
|
||||
) {
|
||||
return await this.model.findOneAndUpdate(filter, update, option);
|
||||
}
|
||||
|
||||
async findAllByStatus(filter: FilterQuery<ClaimRequestManagementModel>) {
|
||||
return await this.model.find(filter);
|
||||
}
|
||||
|
||||
async findAndDelete(
|
||||
filter: FilterQuery<ClaimRequestManagementModel>,
|
||||
option,
|
||||
) {
|
||||
return await this.model.deleteMany(filter, option);
|
||||
}
|
||||
|
||||
async findAllAndPagination(
|
||||
filter: FilterQuery<ClaimRequestManagementModel>,
|
||||
currentPage: number,
|
||||
countPerPage: number,
|
||||
) {
|
||||
const responsePerPage = countPerPage | 1;
|
||||
const skipPge = responsePerPage * (Number(currentPage) - 1);
|
||||
return await this.model.find(filter).limit(responsePerPage).skip(skipPge);
|
||||
}
|
||||
|
||||
async aggregate(filter?) {
|
||||
return await this.model.aggregate(filter);
|
||||
}
|
||||
|
||||
async findAndUpdate(
|
||||
id: string,
|
||||
update: UpdateQuery<ClaimRequestManagementModel>,
|
||||
option?: FilterQuery<ClaimRequestManagementModel>,
|
||||
) {
|
||||
return await this.model.findByIdAndUpdate(
|
||||
{ _id: new Types.ObjectId(id) },
|
||||
update,
|
||||
option,
|
||||
);
|
||||
}
|
||||
|
||||
async findAllByAnyFilter(
|
||||
filter: FilterQuery<ClaimRequestManagementModel>,
|
||||
): Promise<ClaimRequestManagementModel[]> {
|
||||
return await this.model.find(filter);
|
||||
}
|
||||
|
||||
async countByFilter(
|
||||
filter: FilterQuery<ClaimRequestManagementModel>,
|
||||
): Promise<number> {
|
||||
return await this.model.countDocuments(filter);
|
||||
}
|
||||
async findAll(): Promise<ClaimRequestManagementModel[]> {
|
||||
return await this.model.find();
|
||||
}
|
||||
async generateUniqueNumbers(digits = 5) {
|
||||
try {
|
||||
const max = Math.pow(10, digits);
|
||||
const randomBytes = crypto.randomBytes(Math.ceil(digits / 2));
|
||||
const randomNumber = parseInt(randomBytes.toString("hex"), 16) % max;
|
||||
return randomNumber.toString().padStart(digits, "0");
|
||||
} catch (error) {
|
||||
console.error("Error generating unique numbers:", error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
async findAllWithFilter(filter?: FilterQuery<ClaimRequestManagementModel>) {
|
||||
return await this.model.find(filter);
|
||||
}
|
||||
|
||||
async findByIdAndUpdate(
|
||||
id: string,
|
||||
updateDto: UpdateQuery<ClaimRequestManagementModel>,
|
||||
): Promise<ClaimRequestManagementModel | null> {
|
||||
return this.model.findByIdAndUpdate(id, updateDto, { new: true }).lean();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
import { Injectable } from "@nestjs/common";
|
||||
import { InjectModel } from "@nestjs/mongoose";
|
||||
import { FilterQuery, Model, Types } from "mongoose";
|
||||
import { ClaimRequiredDocument } from "src/claim-request-management/entites/schema/claim-required-document.schema";
|
||||
|
||||
@Injectable()
|
||||
export class ClaimRequiredDocumentDbService {
|
||||
constructor(
|
||||
@InjectModel(ClaimRequiredDocument.name)
|
||||
private readonly model: Model<ClaimRequiredDocument>,
|
||||
) {}
|
||||
|
||||
async create(document: Partial<ClaimRequiredDocument>): Promise<ClaimRequiredDocument> {
|
||||
return await this.model.create(document);
|
||||
}
|
||||
|
||||
async findOne(
|
||||
filter: FilterQuery<ClaimRequiredDocument>,
|
||||
): Promise<ClaimRequiredDocument | null> {
|
||||
return await this.model.findOne(filter);
|
||||
}
|
||||
|
||||
async findAll(
|
||||
filter: FilterQuery<ClaimRequiredDocument>,
|
||||
): Promise<ClaimRequiredDocument[]> {
|
||||
return await this.model.find(filter);
|
||||
}
|
||||
|
||||
async findById(id: string): Promise<ClaimRequiredDocument | null> {
|
||||
return this.model.findById(id).lean();
|
||||
}
|
||||
|
||||
async findByClaimId(claimId: string): Promise<ClaimRequiredDocument[]> {
|
||||
return this.model.find({ claimId: new Types.ObjectId(claimId) });
|
||||
}
|
||||
|
||||
async delete(id: string): Promise<void> {
|
||||
await this.model.findByIdAndDelete(id);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
import { Injectable } from "@nestjs/common";
|
||||
import { InjectModel } from "@nestjs/mongoose";
|
||||
import { FilterQuery, Model } from "mongoose";
|
||||
import { ClaimSignModel } from "src/claim-request-management/entites/schema/claim-sign";
|
||||
|
||||
@Injectable()
|
||||
export class ClaimSignDbService {
|
||||
constructor(
|
||||
@InjectModel(ClaimSignModel.name)
|
||||
private readonly claimSignService: Model<ClaimSignModel>,
|
||||
) {}
|
||||
|
||||
async create(claimSign: ClaimSignModel): Promise<ClaimSignModel> {
|
||||
return await this.claimSignService.create(claimSign);
|
||||
}
|
||||
|
||||
async findOne(filter: FilterQuery<ClaimSignModel>): Promise<ClaimSignModel> {
|
||||
return await this.claimSignService.findOne(filter);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
import { InjectModel } from "@nestjs/mongoose";
|
||||
import { Model, Types } from "mongoose";
|
||||
import { DamagePartImageModel } from "src/claim-request-management/entites/schema/damage-image-part.schema";
|
||||
|
||||
export class DamageImageDbService {
|
||||
constructor(
|
||||
@InjectModel(DamagePartImageModel.name)
|
||||
private readonly model: Model<DamagePartImageModel>,
|
||||
) {}
|
||||
|
||||
async create(image): Promise<DamagePartImageModel> {
|
||||
return await this.model.create(image);
|
||||
}
|
||||
|
||||
async findOne(id: string): Promise<DamagePartImageModel> {
|
||||
return await this.model.findById(new Types.ObjectId(id));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
import { Injectable } from "@nestjs/common";
|
||||
import { InjectModel } from "@nestjs/mongoose";
|
||||
import { FilterQuery, Model } from "mongoose";
|
||||
import { ClaimFactorsImage } from "src/claim-request-management/entites/schema/factor-image.schema";
|
||||
|
||||
@Injectable()
|
||||
export class ClaimFactorsImageDbService {
|
||||
constructor(
|
||||
@InjectModel(ClaimFactorsImage.name)
|
||||
private readonly model: Model<ClaimFactorsImage>,
|
||||
) {}
|
||||
async create(image): Promise<ClaimFactorsImage> {
|
||||
return await this.model.create(image);
|
||||
}
|
||||
|
||||
async findOne(
|
||||
filter: FilterQuery<ClaimFactorsImage>,
|
||||
): Promise<ClaimFactorsImage> {
|
||||
return await this.model.findOne({ filter });
|
||||
}
|
||||
|
||||
async findById(id: string): Promise<ClaimFactorsImage | null> {
|
||||
return this.model.findById(id).lean();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
import { Injectable } from "@nestjs/common";
|
||||
import { InjectModel } from "@nestjs/mongoose";
|
||||
import { FilterQuery, Model } from "mongoose";
|
||||
import { VideoCaptureModel } from "src/claim-request-management/entites/schema/video-capture.schema";
|
||||
|
||||
@Injectable()
|
||||
export class VideoCaptureDbService {
|
||||
constructor(
|
||||
@InjectModel(VideoCaptureModel.name)
|
||||
private readonly videoCapture: Model<VideoCaptureModel>,
|
||||
) {}
|
||||
async create(video): Promise<VideoCaptureModel> {
|
||||
return await this.videoCapture.create(video);
|
||||
}
|
||||
|
||||
async findOne(
|
||||
filter: FilterQuery<VideoCaptureModel>,
|
||||
): Promise<VideoCaptureModel> {
|
||||
return await this.videoCapture.findOne(filter);
|
||||
}
|
||||
|
||||
async findById(id: string): Promise<VideoCaptureModel | null> {
|
||||
return this.videoCapture.findById(id).lean();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
import { Prop, Schema, SchemaFactory } from "@nestjs/mongoose";
|
||||
import mongoose, { Types } from "mongoose";
|
||||
|
||||
@Schema({ versionKey: false })
|
||||
export class ActionUserModel extends mongoose.Document {
|
||||
@Prop({ required: true, type: Types.ObjectId })
|
||||
userId: Types.ObjectId;
|
||||
|
||||
@Prop({ required: true, type: Date })
|
||||
date: Date;
|
||||
}
|
||||
|
||||
export const ActionActorSchema = SchemaFactory.createForClass(ActionUserModel);
|
||||
@@ -0,0 +1,7 @@
|
||||
import { Prop, Schema } from "@nestjs/mongoose";
|
||||
|
||||
@Schema({ versionKey: false, _id: true })
|
||||
export class AiImagesModel {
|
||||
@Prop({ type: "array" })
|
||||
imagesAddress: string[];
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
import { Prop, Schema, SchemaFactory } from "@nestjs/mongoose";
|
||||
import mongoose, { Types } from "mongoose";
|
||||
|
||||
@Schema({ versionKey: false, collection: "car-green-cards" })
|
||||
export class CarGreenCardModel extends mongoose.Document {
|
||||
@Prop({ required: false, type: String })
|
||||
path?: string;
|
||||
|
||||
@Prop({ required: false, type: String })
|
||||
fileName?: string;
|
||||
|
||||
@Prop({ required: false, type: Types.ObjectId })
|
||||
claimId?: Types.ObjectId;
|
||||
}
|
||||
|
||||
export const CarGreenCardSchema =
|
||||
SchemaFactory.createForClass(CarGreenCardModel);
|
||||
@@ -0,0 +1,18 @@
|
||||
import { Prop } from "@nestjs/mongoose";
|
||||
|
||||
export class CarDamagePartOtherModel {
|
||||
@Prop({
|
||||
format: "array",
|
||||
description: "please add items of json into array",
|
||||
example: [{ "حسگر درها": true }],
|
||||
})
|
||||
otherParts?: [];
|
||||
}
|
||||
|
||||
export class CarDamagePartModel {
|
||||
@Prop({ type: String })
|
||||
side: string;
|
||||
|
||||
@Prop({ type: String })
|
||||
part: string;
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
import { Prop } from "@nestjs/mongoose";
|
||||
import mongoose, { Types } from "mongoose";
|
||||
import { ActionUserModel } from "./action-user.schema";
|
||||
|
||||
export class ClaimBaseModel extends mongoose.Document {
|
||||
@Prop()
|
||||
readonly _id: Types.ObjectId;
|
||||
|
||||
@Prop()
|
||||
readonly created: Date;
|
||||
|
||||
@Prop({
|
||||
required: false,
|
||||
type: ActionUserModel,
|
||||
})
|
||||
createdBy: ActionUserModel;
|
||||
|
||||
@Prop({ required: false })
|
||||
readonly updated: Date;
|
||||
|
||||
@Prop({
|
||||
required: false,
|
||||
type: [ActionUserModel],
|
||||
})
|
||||
updatedBy: ActionUserModel[];
|
||||
|
||||
@Prop()
|
||||
readonly deleted: boolean;
|
||||
}
|
||||
@@ -0,0 +1,330 @@
|
||||
import { Prop, Schema, SchemaFactory } from "@nestjs/mongoose";
|
||||
import { Types } from "mongoose";
|
||||
import {
|
||||
CarDetail,
|
||||
RequestManagementModel,
|
||||
} from "src/request-management/entities/schema/request-management.schema";
|
||||
import { UserSignModel } from "src/request-management/entities/schema/sign.schema";
|
||||
import { ReqClaimStatus } from "src/Types&Enums/claim-request-management/status.enum";
|
||||
import { ClaimStepsEnum } from "src/Types&Enums/claim-request-management/steps.enum";
|
||||
import { UserReplyEnum } from "src/Types&Enums/claim-request-management/userReply.enum";
|
||||
import { AiImagesModel } from "./ai-image.schema";
|
||||
import { CarGreenCardModel } from "./car-green-card.schema";
|
||||
import {
|
||||
CarDamagePartModel,
|
||||
CarDamagePartOtherModel,
|
||||
} from "./car-parts.schema";
|
||||
import { ImageRequiredModel } from "./image-required.schema";
|
||||
import { Plates } from "src/Types&Enums/plate.interface";
|
||||
import { AddPlateDto } from "src/profile/dto/user/AddPlateDto";
|
||||
import { FactorStatus } from "src/Types&Enums/claim-request-management/factor-status.enum";
|
||||
import { DaghiOption } from "src/Types&Enums/claim-request-management/daghi-option.enum";
|
||||
|
||||
// main schema
|
||||
export type ClaimRequestManagementDoc = ClaimRequestManagementModel & Document;
|
||||
|
||||
export class EffectedUserReply {
|
||||
@Prop({ required: true, type: String })
|
||||
reply?: UserReplyEnum;
|
||||
|
||||
@Prop({ required: true })
|
||||
signDetail?: UserSignModel;
|
||||
}
|
||||
|
||||
export class UserComment {
|
||||
@Prop({ type: Boolean })
|
||||
isAccept: boolean;
|
||||
|
||||
@Prop({ required: false })
|
||||
signDetail?: Types.ObjectId;
|
||||
}
|
||||
|
||||
export class DaghiDetails {
|
||||
@Prop({ required: true, type: String, enum: DaghiOption })
|
||||
option: DaghiOption;
|
||||
|
||||
@Prop({ required: false, type: String })
|
||||
price?: string;
|
||||
|
||||
@Prop({ required: false, type: Types.ObjectId })
|
||||
branchId?: Types.ObjectId;
|
||||
}
|
||||
|
||||
export class PartsList {
|
||||
@Prop({ required: true, type: String })
|
||||
partId: string;
|
||||
|
||||
@Prop({ required: true, type: String })
|
||||
carPartDamage: string;
|
||||
|
||||
@Prop({ required: true, type: String })
|
||||
typeOfDamage: string;
|
||||
|
||||
@Prop({ required: true, type: String })
|
||||
price: string;
|
||||
|
||||
@Prop({ required: true, type: String })
|
||||
salary: string;
|
||||
|
||||
@Prop({ required: true, type: String })
|
||||
totalPayment: string;
|
||||
|
||||
@Prop({ required: true, type: DaghiDetails })
|
||||
daghi: DaghiDetails;
|
||||
|
||||
@Prop({ required: true, type: Boolean })
|
||||
factorNeeded: boolean;
|
||||
|
||||
@Prop({ type: Types.ObjectId })
|
||||
factorLink?: Types.ObjectId;
|
||||
|
||||
@Prop({ type: String, enum: FactorStatus, default: null })
|
||||
factorStatus?: FactorStatus;
|
||||
|
||||
@Prop({ type: String, default: null })
|
||||
rejectionReason?: string;
|
||||
}
|
||||
|
||||
export class ActorDetail {
|
||||
@Prop()
|
||||
actorName: string;
|
||||
|
||||
@Prop()
|
||||
actorId: string;
|
||||
}
|
||||
|
||||
export class InPersonDocuments {
|
||||
@Prop()
|
||||
NationalCertificate: string;
|
||||
|
||||
@Prop()
|
||||
CarCertificate: string;
|
||||
|
||||
@Prop()
|
||||
DrivingLicense: string;
|
||||
|
||||
@Prop()
|
||||
CarGreenCard: string;
|
||||
}
|
||||
|
||||
export class SubmitReply {
|
||||
@Prop({ required: true })
|
||||
description: string;
|
||||
|
||||
@Prop({ required: true })
|
||||
actorDetail: ActorDetail;
|
||||
|
||||
@Prop({ required: true, type: [PartsList] })
|
||||
parts: PartsList[];
|
||||
|
||||
@Prop({ required: true, default: () => new Date() })
|
||||
submitTime: Date;
|
||||
|
||||
@Prop()
|
||||
userComment?: UserComment;
|
||||
}
|
||||
|
||||
export class ActorLockDetails {
|
||||
@Prop({ type: String })
|
||||
fullName?: string;
|
||||
|
||||
@Prop({ type: Types.ObjectId })
|
||||
actorId?: Types.ObjectId;
|
||||
}
|
||||
|
||||
export class ResendCarPartsDto {
|
||||
@Prop({ required: true })
|
||||
partId: string;
|
||||
|
||||
@Prop({ required: true })
|
||||
partName: string;
|
||||
}
|
||||
|
||||
export class ClaimSubmitResend {
|
||||
@Prop({ required: true })
|
||||
resendDescription: string;
|
||||
|
||||
@Prop({ required: true, type: [InPersonDocuments] })
|
||||
resendDocuments: InPersonDocuments[];
|
||||
|
||||
@Prop({ required: true })
|
||||
resendCarParts: ResendCarPartsDto[];
|
||||
}
|
||||
|
||||
export class UserResendDocuments {
|
||||
@Prop({ required: false })
|
||||
documents: [];
|
||||
|
||||
@Prop({ required: false })
|
||||
carParts: [];
|
||||
}
|
||||
|
||||
export class UserObjection {
|
||||
@Prop({ type: String, required: true })
|
||||
partId: string;
|
||||
|
||||
@Prop({ type: String })
|
||||
reason: string;
|
||||
|
||||
@Prop({ type: String })
|
||||
partPrice: string;
|
||||
|
||||
@Prop({ type: String })
|
||||
partSalary: string;
|
||||
|
||||
@Prop({ type: String })
|
||||
typeOfDamage: string;
|
||||
}
|
||||
|
||||
@Schema({ _id: false })
|
||||
export class FileRating {
|
||||
@Prop({ type: Number, min: 0, max: 5 })
|
||||
collisionMethodAccuracy: number;
|
||||
|
||||
@Prop({ type: Number, min: 0, max: 5 })
|
||||
evaluationTimeliness: number;
|
||||
|
||||
@Prop({ type: Number, min: 0, max: 5 })
|
||||
accidentCauseAccuracy: number;
|
||||
|
||||
@Prop({ type: Number, min: 0, max: 5 })
|
||||
guiltyVehicleIdentification: number;
|
||||
}
|
||||
|
||||
export class PriceDrop {
|
||||
@Prop({ type: Number })
|
||||
total: number;
|
||||
@Prop({ type: Number })
|
||||
carPrice: number;
|
||||
@Prop({ type: Number })
|
||||
carModel: number;
|
||||
@Prop({ type: [Number] })
|
||||
carValue: number[];
|
||||
@Prop({ type: Number })
|
||||
sumOfSeverity: number;
|
||||
}
|
||||
|
||||
@Schema({
|
||||
collection: "claim-requests-management",
|
||||
versionKey: false,
|
||||
timestamps: true,
|
||||
autoIndex: false,
|
||||
})
|
||||
export class ClaimRequestManagementModel {
|
||||
readonly aiImage?: any;
|
||||
constructor() {}
|
||||
@Prop({ type: RequestManagementModel, unique: true })
|
||||
blameFile: RequestManagementModel;
|
||||
|
||||
@Prop({ type: Number, default: 100 })
|
||||
requestNumber?: number;
|
||||
|
||||
@Prop({ type: Types.ObjectId })
|
||||
userClientKey?: Types.ObjectId;
|
||||
|
||||
@Prop({ type: Types.ObjectId })
|
||||
userId: Types.ObjectId;
|
||||
|
||||
@Prop()
|
||||
fullName: string;
|
||||
|
||||
@Prop()
|
||||
carDetail: CarDetail;
|
||||
|
||||
@Prop({ type: AddPlateDto })
|
||||
carPlate: Plates;
|
||||
|
||||
@Prop({})
|
||||
claimStatus: ReqClaimStatus;
|
||||
|
||||
@Prop({})
|
||||
steps: ClaimStepsEnum[];
|
||||
|
||||
@Prop({})
|
||||
currentStep: ClaimStepsEnum;
|
||||
|
||||
@Prop({})
|
||||
nextStep?: ClaimStepsEnum;
|
||||
|
||||
@Prop({ type: CarDamagePartModel })
|
||||
carPartDamage?: CarDamagePartModel[];
|
||||
|
||||
@Prop({ type: CarDamagePartOtherModel })
|
||||
otherParts?: CarDamagePartOtherModel[];
|
||||
|
||||
@Prop({ format: "string" })
|
||||
sheba?: string;
|
||||
|
||||
@Prop({ format: "string" })
|
||||
nationalCodeOfInsurer?: string;
|
||||
|
||||
@Prop({ type: CarGreenCardModel, required: false })
|
||||
carGreenCard?: CarGreenCardModel;
|
||||
|
||||
@Prop({ type: ImageRequiredModel })
|
||||
imageRequired?: ImageRequiredModel;
|
||||
|
||||
@Prop({ type: AiImagesModel })
|
||||
aiImages?: AiImagesModel;
|
||||
|
||||
@Prop({ type: EffectedUserReply })
|
||||
effectedUserReply?: EffectedUserReply;
|
||||
|
||||
@Prop()
|
||||
lockFile?: boolean;
|
||||
|
||||
@Prop({ type: Date })
|
||||
lockTime?: Date | string | number;
|
||||
|
||||
@Prop({ type: Date })
|
||||
unlockTime?: Date | number;
|
||||
|
||||
@Prop()
|
||||
actorLocked?: ActorLockDetails | null;
|
||||
|
||||
@Prop()
|
||||
actorsChecker?: [];
|
||||
|
||||
@Prop({ required: false })
|
||||
videoCaptureId?: Types.ObjectId;
|
||||
|
||||
@Prop({ required: false })
|
||||
damageExpertReply?: SubmitReply | null;
|
||||
|
||||
@Prop({ required: false })
|
||||
damageExpertReplyFinal?: SubmitReply | null;
|
||||
|
||||
@Prop({ required: false, type: ClaimSubmitResend })
|
||||
damageExpertResend?: ClaimSubmitResend | null;
|
||||
|
||||
@Prop({ type: UserObjection, required: false })
|
||||
objection?: UserObjection;
|
||||
|
||||
@Prop({ type: UserResendDocuments })
|
||||
userResendDocuments?: UserResendDocuments;
|
||||
|
||||
@Prop({ type: PriceDrop, required: false })
|
||||
priceDrop?: PriceDrop;
|
||||
|
||||
@Prop({ type: Map, of: Types.ObjectId, required: false })
|
||||
requiredDocuments?: { [key: string]: Types.ObjectId };
|
||||
|
||||
createdAt: any;
|
||||
updatedAt: any;
|
||||
|
||||
@Prop({ type: FileRating, required: false })
|
||||
rating?: FileRating;
|
||||
|
||||
@Prop({ type: String, required: false })
|
||||
visitLocation?: string;
|
||||
|
||||
@Prop({ type: Number, required: false })
|
||||
claimNo?: number;
|
||||
|
||||
@Prop({ type: Number, required: false })
|
||||
claimId?: number;
|
||||
}
|
||||
|
||||
export const ClaimRequestManagementSchema = SchemaFactory.createForClass(
|
||||
ClaimRequestManagementModel,
|
||||
);
|
||||
@@ -0,0 +1,27 @@
|
||||
import { Prop, Schema, SchemaFactory } from "@nestjs/mongoose";
|
||||
import { Types } from "mongoose";
|
||||
import { ClaimRequiredDocumentType } from "src/Types&Enums/claim-request-management/required-document-type.enum";
|
||||
|
||||
@Schema({ versionKey: false, collection: "claim-required-documents" })
|
||||
export class ClaimRequiredDocument {
|
||||
@Prop({ required: true, type: String })
|
||||
path: string;
|
||||
|
||||
@Prop({ required: true, type: String })
|
||||
fileName: string;
|
||||
|
||||
@Prop({ required: true, type: Types.ObjectId })
|
||||
claimId: Types.ObjectId;
|
||||
|
||||
@Prop({ required: true, enum: ClaimRequiredDocumentType })
|
||||
documentType: ClaimRequiredDocumentType;
|
||||
|
||||
@Prop({ default: () => new Date() })
|
||||
uploadedAt: Date;
|
||||
|
||||
_id?: Types.ObjectId;
|
||||
}
|
||||
|
||||
export const ClaimRequiredDocumentSchema =
|
||||
SchemaFactory.createForClass(ClaimRequiredDocument);
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
import { Schema, SchemaFactory } from "@nestjs/mongoose";
|
||||
import { UserSignModel } from "src/request-management/entities/schema/sign.schema";
|
||||
|
||||
@Schema({ collection: "claim-sign", versionKey: false })
|
||||
export class ClaimSignModel extends UserSignModel {}
|
||||
export const ClaimSignSchema = SchemaFactory.createForClass(ClaimSignModel);
|
||||
@@ -0,0 +1,18 @@
|
||||
import { Prop, Schema, SchemaFactory } from "@nestjs/mongoose";
|
||||
|
||||
export type DamagePartImage = DamagePartImageModel & Document;
|
||||
|
||||
@Schema({ versionKey: false, collection: "damage-part-image" })
|
||||
export class DamagePartImageModel {
|
||||
@Prop({ default: null })
|
||||
path: string | null = null;
|
||||
|
||||
@Prop({ default: null })
|
||||
fileName: string | null = null;
|
||||
|
||||
@Prop({ default: null })
|
||||
claimId: string = null;
|
||||
}
|
||||
|
||||
export const DamageImageModelSchema =
|
||||
SchemaFactory.createForClass(DamagePartImageModel);
|
||||
@@ -0,0 +1,28 @@
|
||||
import { Prop, Schema, SchemaFactory } from "@nestjs/mongoose";
|
||||
import { Types } from "mongoose";
|
||||
|
||||
@Schema({ versionKey: false, collection: "claim-factors-image" })
|
||||
export class ClaimFactorsImage {
|
||||
@Prop({ required: true, type: String })
|
||||
path: string;
|
||||
|
||||
@Prop({ required: true, type: String })
|
||||
fileName: string;
|
||||
|
||||
@Prop({ required: true, type: Types.ObjectId })
|
||||
claimId: Types.ObjectId;
|
||||
|
||||
@Prop({ required: true, type: String })
|
||||
partId: string;
|
||||
|
||||
@Prop({ required: true, type: Object })
|
||||
partName: any;
|
||||
|
||||
@Prop({ default: () => new Date() })
|
||||
uploadedAt: Date;
|
||||
|
||||
_id?: Types.ObjectId;
|
||||
}
|
||||
|
||||
export const ClaimFactorsImageSchema =
|
||||
SchemaFactory.createForClass(ClaimFactorsImage);
|
||||
@@ -0,0 +1,38 @@
|
||||
import { Prop, Schema } from "@nestjs/mongoose";
|
||||
import { v4 as uuidv4 } from "uuid";
|
||||
|
||||
@Schema({ versionKey: false, _id: false })
|
||||
export class ImageRequiredModel {
|
||||
@Prop({ type: "array" })
|
||||
public aroundTheCar = [
|
||||
{ side: "left" },
|
||||
{ side: "back" },
|
||||
{ side: "front" },
|
||||
{ side: "right" },
|
||||
];
|
||||
|
||||
@Prop({ type: "array" })
|
||||
public selectPartOfCar = [];
|
||||
|
||||
@Prop({ type: "string" })
|
||||
public aiReportText: string;
|
||||
|
||||
constructor(claimFile: any[]) {
|
||||
this.aroundTheCar.forEach((a) => {
|
||||
Object.assign(a, {
|
||||
partId: uuidv4(),
|
||||
imageId: null,
|
||||
aiReport: {},
|
||||
upload: false,
|
||||
});
|
||||
});
|
||||
this.selectPartOfCar = claimFile.map((c, idx) =>
|
||||
Object.assign(c, {
|
||||
partId: uuidv4(),
|
||||
aiReport: {},
|
||||
imageId: null,
|
||||
upload: false,
|
||||
}),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
import { Prop, Schema, SchemaFactory } from "@nestjs/mongoose";
|
||||
import mongoose, { Types } from "mongoose";
|
||||
|
||||
@Schema({ versionKey: false, collection: "claim-video-capture" })
|
||||
export class VideoCaptureModel extends mongoose.Document {
|
||||
@Prop({ required: false, type: String })
|
||||
path?: string;
|
||||
|
||||
@Prop({ required: false, type: String })
|
||||
fileName?: string;
|
||||
|
||||
@Prop({ required: false, type: Types.ObjectId })
|
||||
claimId?: Types.ObjectId;
|
||||
}
|
||||
|
||||
export const VideoCaptureSchema =
|
||||
SchemaFactory.createForClass(VideoCaptureModel);
|
||||
181
src/claim-request-management/utils/car-part.json
Normal file
181
src/claim-request-management/utils/car-part.json
Normal file
@@ -0,0 +1,181 @@
|
||||
{
|
||||
"قاب موتور": true,
|
||||
"گلگیرهاتایرها": true,
|
||||
"دیسک ترمز": true,
|
||||
"لنت ترمز": true,
|
||||
"روغن ترمز": true,
|
||||
"ترمز دستی": true,
|
||||
"پیچ چرخها": true,
|
||||
"پیستون ترمز": true,
|
||||
"پدال ترمز": true,
|
||||
"شلنگهای ترمز": true,
|
||||
"سوزن هواگیر": true,
|
||||
"سامانه ABS": true,
|
||||
"پمپ ترمز": true,
|
||||
"کالیپر": true,
|
||||
"بوستر": true,
|
||||
"انواع سوپاپهای ترمز": true,
|
||||
"فنرهای نگهدارنده": true,
|
||||
"لنگر": true,
|
||||
"کفشک ترمز": true,
|
||||
"مخزن روغن ترمز": true,
|
||||
"واحد تقویتکننده هیدرولیکی": true,
|
||||
"پایه باتری": true,
|
||||
"کابل باتری": true,
|
||||
"سینی باتری": true,
|
||||
"سامانه مدیریت باتری": true,
|
||||
"دینام": true,
|
||||
"ترمینال سیمکشی باتری": true,
|
||||
"چراغهای جلو و عقب": true,
|
||||
"چراغهای مهشکن": true,
|
||||
"چراغهایی داخل داشبورد": true,
|
||||
"چراغ سقفی داخل کابین": true,
|
||||
"حسگر دنده اتوماتیک": true,
|
||||
"حسگر سرعت": true,
|
||||
"حسگر دمای مایع خنککننده": true,
|
||||
"حسگر ترمز ABS": true,
|
||||
"حسگر اکسیژن": true,
|
||||
"حسگر جریان هوا": true,
|
||||
"حسگر کیسه هوا": true,
|
||||
"حسگر روغن": true,
|
||||
"حسگر سوخت": true,
|
||||
"حسگر میللنگ": true,
|
||||
"حسگر میل بادامک": true,
|
||||
"حسگر کمربند ایمنی": true,
|
||||
"حسگر نور": true,
|
||||
"حسگر درها": true,
|
||||
"جعبه احتراق": true,
|
||||
"شمع": true,
|
||||
"دلکو": true,
|
||||
"کنترلگر زمان": true,
|
||||
"سیمهای اتصال": true,
|
||||
"سوپاپ مغناطیسی": true,
|
||||
"سیمپیچ احتراق": true,
|
||||
"وایر شمع": true,
|
||||
"رله فن": true,
|
||||
"سوئیچ درها": true,
|
||||
"سوئیچ استارت": true,
|
||||
"کلید شیشه بالابر": true,
|
||||
"سوئیچ قفل فرمان": true,
|
||||
"ترموستات": true,
|
||||
"تهویه": true,
|
||||
"موتور": true,
|
||||
"کف کابین": true,
|
||||
"ادوات داخل کابین": true,
|
||||
"اصلی": true,
|
||||
"دوربین دنده عقب": true,
|
||||
"دوربینهای 360 درجه": true,
|
||||
"صال به زمین": true,
|
||||
"سامانه قفل مرکزی": true,
|
||||
"اتصالات برقی درها": true,
|
||||
"ماژول کنترل ایربگ": true,
|
||||
"سامانه کنترل سرعت": true,
|
||||
"سامانه مدیریت موتور": true,
|
||||
"کروز کنترل": true,
|
||||
"سامانه ناوبری": true,
|
||||
"سوکتها": true,
|
||||
"سیستم قفل از راه دور": true,
|
||||
"رایانه جعبهدنده": true,
|
||||
"فیوزها": true,
|
||||
"بلوک سیلندر": true,
|
||||
"پوشش میللنگ": true,
|
||||
"پولی میللنگ": true,
|
||||
"میللنگ": true,
|
||||
"پولی پمپ آب": true,
|
||||
"تسمه پروانه": true,
|
||||
"پیستون": true,
|
||||
"تسمه دینام": true,
|
||||
"تسمه تایم": true,
|
||||
"توربو شارژ": true,
|
||||
"درپوش سوپاپ": true,
|
||||
"دسته موتور": true,
|
||||
"سرسیلندر": true,
|
||||
"سوپاپ پایت": true,
|
||||
"سوپاپ تهویه": true,
|
||||
"شاتون": true,
|
||||
"پین انگشتی": true,
|
||||
"بخاری": true,
|
||||
"میل بادامک": true,
|
||||
"لرزشگیر موتور": true,
|
||||
"فیلر": true,
|
||||
"جعبهدنده": true,
|
||||
"پوسته گیربکس": true,
|
||||
"چرخدندههای انتقال قدرت، جناحی، هرزگرد، سرعتسنج، چرخ لنگر، فرمان": true,
|
||||
"پمپ دنده": true,
|
||||
"دنده": true,
|
||||
"اهرم تعویض دنده": true,
|
||||
"دوشاخ دنده": true,
|
||||
"کوپلینگ دنده": true,
|
||||
"دیفرانسیل": true,
|
||||
"سیلندر": true,
|
||||
"فنر جعبهدنده": true,
|
||||
"محور جعبهدنده": true,
|
||||
"میلگاردان": true,
|
||||
"شفت خروجی": true,
|
||||
"پولوسها": true,
|
||||
"سامانه کلاچ": true,
|
||||
"کابل تعویض دنده": true,
|
||||
"فیلتر بنزین": true,
|
||||
"فیلتر هوا": true,
|
||||
"کاربراتور": true,
|
||||
"باک سوخت": true,
|
||||
"سیستم LPG": true,
|
||||
"کابل ساسات": true,
|
||||
"منیفولد ورودی": true,
|
||||
"جداکننده آب از سوخت": true,
|
||||
"پیل سوختی": true,
|
||||
"سیستم CNG": true,
|
||||
"پمپبنزین": true,
|
||||
"انژکتور": true,
|
||||
"بدنه دریچه گاز": true,
|
||||
"خنککننده سوخت": true,
|
||||
"رگلاتور": true,
|
||||
"ریل": true,
|
||||
"غربیلک فرمان": true,
|
||||
"بازوی فرمان": true,
|
||||
"جعبه فرمان": true,
|
||||
"دوک": true,
|
||||
"شفت فرمان": true,
|
||||
"سامانه فرمان خودکار": true,
|
||||
"اتصال جانبی": true,
|
||||
"اتصال میل موجگیر": true,
|
||||
"بازوی خمیده": true,
|
||||
"بازوی آزاد": true,
|
||||
"بازوی پیت – من": true,
|
||||
"بست و اتصالات": true,
|
||||
"سیبک فرمان": true,
|
||||
"کمکفنرها": true,
|
||||
"اتصالات تعادلی": true,
|
||||
"فنر": true,
|
||||
"محور خودرو": true,
|
||||
"عایق حرارتی": true,
|
||||
"گیرهها": true,
|
||||
"لوله اگزوز": true,
|
||||
"سپر حرارتی": true,
|
||||
"صداخفهکن": true,
|
||||
"رزیناتور": true,
|
||||
"کاتالیست": true,
|
||||
"حلقه فاصله": true,
|
||||
"انواع واشرها": true,
|
||||
"لولههای روغن": true,
|
||||
"کارتل روغن": true,
|
||||
"پمپ روغن": true,
|
||||
"واشر پمپ روغن": true,
|
||||
"صافی روغن": true,
|
||||
"فیلتر روغن": true,
|
||||
"تسمه فن": true,
|
||||
"تیغه فن": true,
|
||||
"واتر پمپ": true,
|
||||
"واشر پمپ آب": true,
|
||||
"دمنده هوا": true,
|
||||
"بوش فن": true,
|
||||
"مخزن": true,
|
||||
"درپوش فشار": true,
|
||||
"ترموستات": true,
|
||||
"پوشش فن": true,
|
||||
"کلاچ فن": true,
|
||||
"پروانه یا فن": true,
|
||||
"لولههای ورودی و خروجی آب": true,
|
||||
"زانویی آب": true,
|
||||
"شلنگ مایع خنککننده": true
|
||||
}
|
||||
37
src/client/client.controller.ts
Normal file
37
src/client/client.controller.ts
Normal file
@@ -0,0 +1,37 @@
|
||||
import {
|
||||
Body,
|
||||
Controller,
|
||||
Get,
|
||||
Post,
|
||||
UseGuards,
|
||||
} from "@nestjs/common";
|
||||
import { ApiBearerAuth, ApiTags } from "@nestjs/swagger";
|
||||
import { GlobalGuard } from "src/auth/guards/global.guard";
|
||||
import { CurrentUser } from "src/decorators/user.decorator";
|
||||
import { ClientService } from "./client.service";
|
||||
import { ClientDto } from "./dto/create-client.dto";
|
||||
|
||||
@Controller("client")
|
||||
@ApiTags("client-management")
|
||||
export class ClientController {
|
||||
constructor(private readonly clientService: ClientService) {}
|
||||
|
||||
@Post()
|
||||
@UseGuards(GlobalGuard)
|
||||
@ApiBearerAuth()
|
||||
async addClient(@Body() client: ClientDto) {
|
||||
return await this.clientService.addClient(client);
|
||||
}
|
||||
|
||||
@Get()
|
||||
@ApiBearerAuth()
|
||||
@UseGuards(GlobalGuard)
|
||||
async getClient(@CurrentUser() user) {
|
||||
return await this.clientService.getClients();
|
||||
}
|
||||
|
||||
@Get("list")
|
||||
async getClientList(@CurrentUser() user) {
|
||||
return await this.clientService.getClientList();
|
||||
}
|
||||
}
|
||||
24
src/client/client.module.ts
Normal file
24
src/client/client.module.ts
Normal file
@@ -0,0 +1,24 @@
|
||||
import { Module } from "@nestjs/common";
|
||||
import { MongooseModule } from "@nestjs/mongoose";
|
||||
import { ClientController } from "./client.controller";
|
||||
import { ClientService } from "./client.service";
|
||||
import { BranchDbService } from "./entities/db-service/branch.db.service";
|
||||
import { ClientDbService } from "./entities/db-service/client.db.service";
|
||||
import { BranchModel, BranchSchema } from "./entities/schema/branch.schema";
|
||||
import { ClientDbSchema, ClientModel } from "./entities/schema/client.schema";
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
MongooseModule.forFeature([
|
||||
{ name: ClientModel.name, schema: ClientDbSchema },
|
||||
{
|
||||
name: BranchModel.name,
|
||||
schema: BranchSchema,
|
||||
},
|
||||
]),
|
||||
],
|
||||
controllers: [ClientController],
|
||||
providers: [ClientService, ClientDbService, BranchDbService],
|
||||
exports: [ClientService, ClientDbService, BranchDbService],
|
||||
})
|
||||
export class ClientModule {}
|
||||
56
src/client/client.service.ts
Normal file
56
src/client/client.service.ts
Normal file
@@ -0,0 +1,56 @@
|
||||
import { BadGatewayException, GoneException, Injectable } from "@nestjs/common";
|
||||
import { Types } from "mongoose";
|
||||
import {
|
||||
ClientDto,
|
||||
ClientDtoRs,
|
||||
ClientLists,
|
||||
} from "src/client/dto/create-client.dto";
|
||||
import { ClientDbService } from "./entities/db-service/client.db.service";
|
||||
|
||||
@Injectable()
|
||||
export class ClientService {
|
||||
constructor(private readonly clientDbService: ClientDbService) {}
|
||||
async addClient(client: ClientDto): Promise<ClientDtoRs> {
|
||||
try {
|
||||
const newClient = await this.clientDbService.create({
|
||||
clientCode: client.clientCode,
|
||||
clientName: {
|
||||
persian: client.clientName.persian,
|
||||
english: client.clientName.english || null,
|
||||
},
|
||||
property: {
|
||||
smsApiKey: client.property.smsApiKey || null,
|
||||
},
|
||||
useExpertMode: client.useExpertMode || null,
|
||||
});
|
||||
if (newClient) return new ClientDtoRs(newClient);
|
||||
else throw new GoneException("database not connected");
|
||||
} catch (er) {
|
||||
throw new BadGatewayException(er.errors);
|
||||
}
|
||||
}
|
||||
|
||||
findOne(filter) {
|
||||
return this.clientDbService.findOne(filter);
|
||||
}
|
||||
|
||||
async findClientWithPersianName(filter: string) {
|
||||
return await this.clientDbService.find({ "clientName.persian": filter });
|
||||
}
|
||||
|
||||
async findClientWithCompanyCode(companyCode: number) {
|
||||
return await this.clientDbService.find({ clientCode: companyCode });
|
||||
}
|
||||
|
||||
async getClients(): Promise<ClientDtoRs[]> {
|
||||
const clients = await this.clientDbService.findAll();
|
||||
const show = clients.map((c) => new ClientDtoRs(c));
|
||||
return show;
|
||||
}
|
||||
|
||||
async getClientList(): Promise<ClientLists[]> {
|
||||
const client = await this.clientDbService.findAll();
|
||||
const list = client.map((element) => new ClientLists(element));
|
||||
return list;
|
||||
}
|
||||
}
|
||||
34
src/client/dto/create-branch.dto.ts
Normal file
34
src/client/dto/create-branch.dto.ts
Normal file
@@ -0,0 +1,34 @@
|
||||
import { ApiProperty } from "@nestjs/swagger";
|
||||
import { IsString, IsNotEmpty, IsOptional } from "class-validator";
|
||||
|
||||
export class CreateBranchDto {
|
||||
@ApiProperty({ example: "شهرک غرب" })
|
||||
@IsString()
|
||||
@IsNotEmpty()
|
||||
name: string;
|
||||
|
||||
@ApiProperty({ example: "1234" })
|
||||
@IsString()
|
||||
@IsNotEmpty()
|
||||
code: string;
|
||||
|
||||
@ApiProperty({ example: "استان" })
|
||||
@IsString()
|
||||
@IsNotEmpty()
|
||||
city: string;
|
||||
|
||||
@ApiProperty({ example: "شهر" })
|
||||
@IsString()
|
||||
@IsNotEmpty()
|
||||
state: string;
|
||||
|
||||
@ApiProperty({ example: "فلان آدرس" })
|
||||
@IsString()
|
||||
@IsNotEmpty()
|
||||
address: string;
|
||||
|
||||
@ApiProperty({ required: false, example: "0912345678" })
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
phoneNumber?: string;
|
||||
}
|
||||
48
src/client/dto/create-client.dto.ts
Normal file
48
src/client/dto/create-client.dto.ts
Normal file
@@ -0,0 +1,48 @@
|
||||
import { Injectable } from "@nestjs/common";
|
||||
import { ApiProperty } from "@nestjs/swagger";
|
||||
import { Types } from "mongoose";
|
||||
|
||||
class ClientName {
|
||||
@ApiProperty({})
|
||||
persian: string;
|
||||
|
||||
@ApiProperty({})
|
||||
english: string;
|
||||
}
|
||||
class Property {
|
||||
@ApiProperty({})
|
||||
smsApiKey: string;
|
||||
}
|
||||
|
||||
@Injectable()
|
||||
export class ClientDto {
|
||||
@ApiProperty({ required: true })
|
||||
clientName: ClientName;
|
||||
|
||||
@ApiProperty({ required: true })
|
||||
clientCode: number;
|
||||
|
||||
@ApiProperty({ required: false })
|
||||
property: Property;
|
||||
|
||||
@ApiProperty({ examples: ["legal", "genuine"] })
|
||||
useExpertMode: "legal" | "genuine";
|
||||
}
|
||||
export class ClientDtoRs {
|
||||
persian: string;
|
||||
english: string;
|
||||
clientId: Types.ObjectId;
|
||||
useExpertsMode: string;
|
||||
constructor(readonly client) {
|
||||
this.persian = client.clientName.persian;
|
||||
}
|
||||
}
|
||||
|
||||
export class ClientLists {
|
||||
name: string;
|
||||
id: Types.ObjectId;
|
||||
constructor(client: ClientDto) {
|
||||
this.name = client.clientName.persian;
|
||||
this.id = client["_id"];
|
||||
}
|
||||
}
|
||||
34
src/client/entities/db-service/branch.db.service.ts
Normal file
34
src/client/entities/db-service/branch.db.service.ts
Normal file
@@ -0,0 +1,34 @@
|
||||
import { Injectable } from "@nestjs/common";
|
||||
import { InjectModel } from "@nestjs/mongoose";
|
||||
import { FilterQuery, Model, Types } from "mongoose";
|
||||
import { BranchModel, BranchDocument } from "../schema/branch.schema";
|
||||
|
||||
@Injectable()
|
||||
export class BranchDbService {
|
||||
constructor(
|
||||
@InjectModel(BranchModel.name)
|
||||
private readonly branchModel: Model<BranchModel>,
|
||||
) {}
|
||||
|
||||
async create(branch: BranchModel): Promise<BranchModel> {
|
||||
return await this.branchModel.create(branch);
|
||||
}
|
||||
|
||||
async find(branch: FilterQuery<BranchModel>): Promise<BranchDocument> {
|
||||
return await this.branchModel.findOne(branch);
|
||||
}
|
||||
|
||||
async findOne(branch: FilterQuery<BranchModel>) {
|
||||
return this.branchModel.findOne(branch);
|
||||
}
|
||||
|
||||
async findAll(insuranceId: string): Promise<BranchModel[]> {
|
||||
return await this.branchModel.find({
|
||||
clientKey: new Types.ObjectId(insuranceId),
|
||||
});
|
||||
}
|
||||
|
||||
async findById(id: string): Promise<BranchModel | null> {
|
||||
return this.branchModel.findById(id).lean();
|
||||
}
|
||||
}
|
||||
28
src/client/entities/db-service/client.db.service.ts
Normal file
28
src/client/entities/db-service/client.db.service.ts
Normal file
@@ -0,0 +1,28 @@
|
||||
import { Injectable } from "@nestjs/common";
|
||||
import { InjectModel } from "@nestjs/mongoose";
|
||||
import { FilterQuery, Model } from "mongoose";
|
||||
import { ClientModel, ClientDocument } from "../schema/client.schema";
|
||||
|
||||
@Injectable()
|
||||
export class ClientDbService {
|
||||
constructor(
|
||||
@InjectModel(ClientModel.name)
|
||||
private readonly clientModel: Model<ClientModel>,
|
||||
) {}
|
||||
|
||||
async create(client: ClientModel): Promise<ClientModel> {
|
||||
return await this.clientModel.create(client);
|
||||
}
|
||||
|
||||
async find(client: FilterQuery<ClientModel>): Promise<ClientDocument> {
|
||||
return await this.clientModel.findOne(client);
|
||||
}
|
||||
|
||||
async findOne(client: FilterQuery<ClientModel>) {
|
||||
return this.clientModel.findOne(client);
|
||||
}
|
||||
|
||||
async findAll(): Promise<ClientModel[]> {
|
||||
return await this.clientModel.find();
|
||||
}
|
||||
}
|
||||
32
src/client/entities/schema/branch.schema.ts
Normal file
32
src/client/entities/schema/branch.schema.ts
Normal file
@@ -0,0 +1,32 @@
|
||||
import { Prop, Schema, SchemaFactory } from "@nestjs/mongoose";
|
||||
import { Types } from "mongoose";
|
||||
|
||||
export type BranchDocument = BranchModel & Document;
|
||||
|
||||
@Schema({ collection: "branches", versionKey: false, timestamps: true })
|
||||
export class BranchModel {
|
||||
@Prop({ required: true, type: Types.ObjectId, ref: "ClientModel" })
|
||||
clientKey: Types.ObjectId;
|
||||
|
||||
@Prop({ required: true })
|
||||
name: string;
|
||||
|
||||
@Prop({ required: true })
|
||||
code: string;
|
||||
|
||||
@Prop({ required: true })
|
||||
city: string;
|
||||
|
||||
@Prop({ required: true })
|
||||
state: string;
|
||||
|
||||
@Prop({ required: true })
|
||||
address: string;
|
||||
|
||||
@Prop()
|
||||
phoneNumber?: string;
|
||||
}
|
||||
|
||||
export const BranchSchema = SchemaFactory.createForClass(BranchModel);
|
||||
|
||||
BranchSchema.index({ clientKey: 1, code: 1 }, { unique: true });
|
||||
25
src/client/entities/schema/client.schema.ts
Normal file
25
src/client/entities/schema/client.schema.ts
Normal file
@@ -0,0 +1,25 @@
|
||||
import { Prop, Schema, SchemaFactory } from "@nestjs/mongoose";
|
||||
|
||||
export type ClientDocument = ClientModel & Document;
|
||||
|
||||
@Schema({ collection: "clients", versionKey: false })
|
||||
export class ClientModel {
|
||||
@Prop({ required: true, unique: true, type: Object })
|
||||
clientName: {
|
||||
persian: string;
|
||||
english: string;
|
||||
};
|
||||
|
||||
@Prop({ required: false, unique: true, type: Object })
|
||||
property: {
|
||||
smsApiKey: string;
|
||||
};
|
||||
|
||||
@Prop({ required: true, unique: false })
|
||||
useExpertMode: "legal" | "genuine";
|
||||
|
||||
@Prop({ required: true, unique: false })
|
||||
clientCode: number;
|
||||
}
|
||||
|
||||
export const ClientDbSchema = SchemaFactory.createForClass(ClientModel);
|
||||
9
src/decorators/clientKey.decorator.ts
Normal file
9
src/decorators/clientKey.decorator.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
import { createParamDecorator, ExecutionContext } from "@nestjs/common";
|
||||
|
||||
export const ClientKey = createParamDecorator(
|
||||
(data: unknown, ctx: ExecutionContext) => {
|
||||
const request = ctx.switchToHttp().getRequest();
|
||||
|
||||
return request.user.clientKey;
|
||||
},
|
||||
);
|
||||
8
src/decorators/customeHeader.decorator.ts
Normal file
8
src/decorators/customeHeader.decorator.ts
Normal file
@@ -0,0 +1,8 @@
|
||||
import { ExecutionContext, createParamDecorator } from "@nestjs/common";
|
||||
|
||||
export const CustomHeader = createParamDecorator(
|
||||
(data, ctx: ExecutionContext) => {
|
||||
console.log(ctx.switchToHttp().getRequest());
|
||||
console.log(ctx.getType());
|
||||
},
|
||||
);
|
||||
4
src/decorators/roles.decorator.ts
Normal file
4
src/decorators/roles.decorator.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
import { SetMetadata } from "@nestjs/common";
|
||||
import { RoleEnum } from "src/Types&Enums/role.enum";
|
||||
|
||||
export const Roles = (...args: RoleEnum[]) => SetMetadata("role", args);
|
||||
8
src/decorators/user.decorator.ts
Normal file
8
src/decorators/user.decorator.ts
Normal file
@@ -0,0 +1,8 @@
|
||||
import { createParamDecorator, ExecutionContext } from "@nestjs/common";
|
||||
|
||||
export const CurrentUser = createParamDecorator(
|
||||
(data: unknown, ctx: ExecutionContext) => {
|
||||
const request = ctx.switchToHttp().getRequest();
|
||||
return request.user;
|
||||
},
|
||||
);
|
||||
103
src/expert-blame/dto/all-request.dto.ts
Normal file
103
src/expert-blame/dto/all-request.dto.ts
Normal file
@@ -0,0 +1,103 @@
|
||||
import { RequestManagementModel } from "src/request-management/entities/schema/request-management.schema";
|
||||
|
||||
export class AllRequestDto {
|
||||
// TODO fix interface for class
|
||||
firstPartyPlate: object;
|
||||
secondPartyPlate: object;
|
||||
secondPartyCar: string | undefined;
|
||||
firstPartyCar: string | undefined;
|
||||
requestId: string;
|
||||
submitTime: string;
|
||||
requestCode: string;
|
||||
date: Date | string;
|
||||
time: Date | string;
|
||||
firstPartyDetail: Object | undefined;
|
||||
secondPartyDetail: Object | undefined;
|
||||
requestStatus: string;
|
||||
lockFile: boolean | undefined;
|
||||
lockTime: any;
|
||||
userComment: boolean | null;
|
||||
status: string;
|
||||
partiesInitialForms: Object;
|
||||
type: string;
|
||||
constructor(request: RequestManagementModel) {
|
||||
// TODO FIX THIS OBJECT AND CLASS FOR CLEAN CODE
|
||||
this.requestId = request["_id"];
|
||||
this.status = request.blameStatus;
|
||||
this.userComment = this.userCommentVoid(request);
|
||||
this.requestCode = request.requestNumber;
|
||||
|
||||
// Format date and time with Iran timezone (Asia/Tehran)
|
||||
if (request.createdAt) {
|
||||
const dateFormatOptions: Intl.DateTimeFormatOptions = {
|
||||
timeZone: "Asia/Tehran",
|
||||
year: "numeric",
|
||||
month: "2-digit",
|
||||
day: "2-digit",
|
||||
};
|
||||
|
||||
const timeFormatOptions: Intl.DateTimeFormatOptions = {
|
||||
timeZone: "Asia/Tehran",
|
||||
hour: "2-digit",
|
||||
minute: "2-digit",
|
||||
second: "2-digit",
|
||||
};
|
||||
|
||||
this.date = `${new Date(request.createdAt).toLocaleDateString("fa-IR", dateFormatOptions)} `;
|
||||
this.time = new Date(request.createdAt).toLocaleTimeString("fa-IR", timeFormatOptions);
|
||||
} else {
|
||||
this.date = "";
|
||||
this.time = "";
|
||||
}
|
||||
|
||||
this.lockFile = request.lockFile;
|
||||
this.lockTime = request.lockTime;
|
||||
this.type = request.type || "THIRD_PARTY"; // Include type field, default to THIRD_PARTY for backward compatibility
|
||||
this.partiesInitialForms = {
|
||||
firstParty: Object.entries(
|
||||
request.firstPartyDetails.firstPartyInitialForm,
|
||||
)
|
||||
.filter(([key, value]) => value)
|
||||
.flat()[0],
|
||||
secondParty: Object.entries(
|
||||
request.secondPartyDetails.secondPartyInitialForm,
|
||||
)
|
||||
.filter(([key, value]) => (value ? key : null))
|
||||
.flat()[0],
|
||||
};
|
||||
this.firstPartyCar =
|
||||
request.firstPartyDetails?.firstPartyCarDetail?.carName;
|
||||
this.secondPartyCar =
|
||||
request.secondPartyDetails?.secondPartyCarDetail?.carName;
|
||||
}
|
||||
|
||||
userCommentVoid(request: RequestManagementModel): boolean | null {
|
||||
if (
|
||||
request?.expertSubmitReply?.firstPartyComment &&
|
||||
request?.expertSubmitReply?.secondPartyComment
|
||||
) {
|
||||
if (
|
||||
request.expertSubmitReply.firstPartyComment.isAccept ||
|
||||
request.expertSubmitReply.secondPartyComment.isAccept
|
||||
) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
getFirstValidKey(obj: Record<string, boolean>) {
|
||||
return (
|
||||
Object.entries(obj).find(([_, value]) => value === true)?.[0] ?? null
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export class AllRequestDtoRs {
|
||||
public data;
|
||||
constructor(requests: RequestManagementModel[]) {
|
||||
this.data = requests.map((r) => new AllRequestDto(r));
|
||||
}
|
||||
}
|
||||
72
src/expert-blame/dto/reply.dto.ts
Normal file
72
src/expert-blame/dto/reply.dto.ts
Normal file
@@ -0,0 +1,72 @@
|
||||
import { ApiProperty } from "@nestjs/swagger";
|
||||
import { Types } from "mongoose";
|
||||
import { BlameDocumentType } from "src/request-management/entities/schema/blame-document.schema";
|
||||
|
||||
export class AccidentWayIF {
|
||||
@ApiProperty({ required: true })
|
||||
id: string;
|
||||
|
||||
@ApiProperty({ required: true })
|
||||
label: string;
|
||||
}
|
||||
export class AccidentReasonIF {
|
||||
@ApiProperty({ required: true })
|
||||
id: string;
|
||||
|
||||
@ApiProperty({ required: true })
|
||||
label: string;
|
||||
|
||||
@ApiProperty({ required: true })
|
||||
fanavaran: number;
|
||||
}
|
||||
|
||||
export class FieldsInterface {
|
||||
@ApiProperty({ required: true })
|
||||
accidentWay: AccidentWayIF;
|
||||
|
||||
@ApiProperty({ required: true })
|
||||
accidentReason: AccidentReasonIF;
|
||||
|
||||
@ApiProperty({ required: true })
|
||||
accidentType: AccidentWayIF;
|
||||
}
|
||||
|
||||
export class SubmitReplyDto {
|
||||
@ApiProperty({ required: true })
|
||||
description: string;
|
||||
|
||||
@ApiProperty({ required: true, type: Types.ObjectId })
|
||||
guiltyUserId: Types.ObjectId;
|
||||
|
||||
@ApiProperty({ required: true })
|
||||
fields: FieldsInterface;
|
||||
}
|
||||
|
||||
export class ResendFirstPartyDto {
|
||||
voice?: string;
|
||||
userReply?: string;
|
||||
@ApiProperty({ required: false })
|
||||
firstPartyId: string | null;
|
||||
|
||||
@ApiProperty({ required: false })
|
||||
firstPartyDescription: string | null;
|
||||
|
||||
documents?: { [key in BlameDocumentType]?: Types.ObjectId | string };
|
||||
}
|
||||
|
||||
export class ResendSecondPartyDto {
|
||||
voice?: string;
|
||||
userReply?: string;
|
||||
@ApiProperty({ required: false })
|
||||
secondPartyId: string | null;
|
||||
|
||||
@ApiProperty({ required: false })
|
||||
secondPartyDescription: string | null;
|
||||
|
||||
documents?: { [key in BlameDocumentType]?: Types.ObjectId | string };
|
||||
}
|
||||
|
||||
export interface SendAginIF {
|
||||
first: ResendFirstPartyDto;
|
||||
second: ResendSecondPartyDto;
|
||||
}
|
||||
156
src/expert-blame/expert-blame.controller.ts
Normal file
156
src/expert-blame/expert-blame.controller.ts
Normal file
@@ -0,0 +1,156 @@
|
||||
import {
|
||||
Controller,
|
||||
Get,
|
||||
Body,
|
||||
Param,
|
||||
UseGuards,
|
||||
Put,
|
||||
Req,
|
||||
Res,
|
||||
Headers,
|
||||
UseInterceptors,
|
||||
Patch,
|
||||
} from "@nestjs/common";
|
||||
import {
|
||||
ApiBearerAuth,
|
||||
ApiBody,
|
||||
ApiOkResponse,
|
||||
ApiParam,
|
||||
ApiProduces,
|
||||
ApiTags,
|
||||
} from "@nestjs/swagger";
|
||||
import { Response, Request } from "express";
|
||||
import { LocalActorAuthGuard } from "src/auth/guards/actor-local.guard";
|
||||
import { RolesGuard } from "src/auth/guards/role.guard";
|
||||
import { ClientKey } from "src/decorators/clientKey.decorator";
|
||||
import { Roles } from "src/decorators/roles.decorator";
|
||||
import { CurrentUser } from "src/decorators/user.decorator";
|
||||
import { LoggingInterceptor } from "src/interceptor/logging.interceptors";
|
||||
import { RoleEnum } from "src/Types&Enums/role.enum";
|
||||
import {
|
||||
ResendFirstPartyDto,
|
||||
ResendSecondPartyDto,
|
||||
SendAginIF,
|
||||
SubmitReplyDto,
|
||||
} from "./dto/reply.dto";
|
||||
import { ExpertBlameService } from "./expert-blame.service";
|
||||
|
||||
@ApiTags("expert-blame-panel")
|
||||
@Controller("expert-blame")
|
||||
@ApiBearerAuth()
|
||||
@UseGuards(LocalActorAuthGuard, RolesGuard)
|
||||
@Roles(RoleEnum.EXPERT)
|
||||
export class ExpertBlameController {
|
||||
constructor(private readonly expertBlameService: ExpertBlameService) {}
|
||||
|
||||
// TODO role guard for expert fix
|
||||
@Roles(RoleEnum.EXPERT)
|
||||
@Get()
|
||||
async findAll(@CurrentUser() actor, @ClientKey() client) {
|
||||
return await this.expertBlameService.findAll(actor);
|
||||
}
|
||||
|
||||
@Roles(RoleEnum.EXPERT)
|
||||
@Get(":id")
|
||||
async findOne(@Param("id") id: string, @CurrentUser() actor) {
|
||||
return await this.expertBlameService.findOne(id, actor.sub);
|
||||
}
|
||||
|
||||
@Roles(RoleEnum.EXPERT)
|
||||
@Put("lock/:id")
|
||||
async lockRequest(@Param("id") id: string, @CurrentUser() actor) {
|
||||
return await this.expertBlameService.lockRequest(id, actor);
|
||||
}
|
||||
|
||||
@Roles(RoleEnum.EXPERT)
|
||||
@Get("request/accident-fields")
|
||||
async getAccidentFields() {
|
||||
return await this.expertBlameService.getAccidentField();
|
||||
}
|
||||
|
||||
@Roles(RoleEnum.EXPERT)
|
||||
@Put("reply/submit/:id")
|
||||
@ApiBody({ type: SubmitReplyDto })
|
||||
@ApiParam({ name: "id" })
|
||||
async submitReply(
|
||||
@Param("id") id: string,
|
||||
@Body() body: SubmitReplyDto,
|
||||
@CurrentUser() actor,
|
||||
) {
|
||||
return await this.expertBlameService.replyRequest(id, body, actor.sub);
|
||||
}
|
||||
|
||||
private async handleResendRequest(
|
||||
id: string,
|
||||
body: SendAginIF,
|
||||
actorSub: string,
|
||||
req: Request,
|
||||
) {
|
||||
return await this.expertBlameService.sendAgainRequest(
|
||||
id,
|
||||
body,
|
||||
actorSub,
|
||||
req,
|
||||
);
|
||||
}
|
||||
|
||||
@Roles(RoleEnum.EXPERT)
|
||||
@Put("reply/resend/first/:id")
|
||||
@ApiBody({ type: ResendFirstPartyDto })
|
||||
@ApiParam({ name: "id" })
|
||||
async resendFirstParty(
|
||||
@Param("id") id: string,
|
||||
@Body() body: SendAginIF,
|
||||
@CurrentUser() actor,
|
||||
@Req() req: Request,
|
||||
) {
|
||||
return this.handleResendRequest(id, body, actor.sub, req);
|
||||
}
|
||||
@Roles(RoleEnum.EXPERT)
|
||||
@Put("reply/resend/second/:id")
|
||||
@Roles(RoleEnum.EXPERT)
|
||||
@ApiBody({ type: ResendSecondPartyDto })
|
||||
@ApiParam({ name: "id" })
|
||||
async resendSecondParty(
|
||||
@Param("id") id: string,
|
||||
@Body() body: SendAginIF,
|
||||
@CurrentUser() actor,
|
||||
@Req() req: Request,
|
||||
) {
|
||||
return this.handleResendRequest(id, body, actor.sub, req);
|
||||
}
|
||||
@Roles(RoleEnum.EXPERT)
|
||||
@Get("stream/:requestId")
|
||||
@ApiParam({ name: "requestId" })
|
||||
async streamVideo(@Param("requestId") requestId: string) {
|
||||
return this.expertBlameService.streamVideo(requestId);
|
||||
}
|
||||
|
||||
@UseInterceptors(LoggingInterceptor)
|
||||
@Get("voice/:requestId/:voiceId")
|
||||
@Roles(RoleEnum.EXPERT, RoleEnum.DAMAGE_EXPERT)
|
||||
@ApiParam({ name: "requestId" })
|
||||
@ApiParam({ name: "voiceId" })
|
||||
@ApiOkResponse({
|
||||
schema: {
|
||||
type: "string",
|
||||
format: "binary",
|
||||
},
|
||||
})
|
||||
@ApiProduces("mp3")
|
||||
async downloadVoice(
|
||||
@Param("voiceId") voiceId,
|
||||
@Param("requestId") requestId: string,
|
||||
@Res({ passthrough: true }) res: Response,
|
||||
@Req() req: Request,
|
||||
@Headers() headers,
|
||||
) {
|
||||
return await this.expertBlameService.streamVoice(requestId, voiceId);
|
||||
}
|
||||
|
||||
@ApiParam({ name: "id" })
|
||||
@Patch(":id/visit")
|
||||
async inPersonVisit(@Param("id") requestId: string, @CurrentUser() actor) {
|
||||
return await this.expertBlameService.inPersonVisit(requestId, actor);
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user