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
|
||||||
logs
|
logs
|
||||||
*.log
|
*.log
|
||||||
@@ -79,7 +79,7 @@ web_modules/
|
|||||||
.env.test.local
|
.env.test.local
|
||||||
.env.production.local
|
.env.production.local
|
||||||
.env.local
|
.env.local
|
||||||
|
.env.development.env
|
||||||
# parcel-bundler cache (https://parceljs.org/)
|
# parcel-bundler cache (https://parceljs.org/)
|
||||||
.cache
|
.cache
|
||||||
.parcel-cache
|
.parcel-cache
|
||||||
@@ -103,13 +103,6 @@ dist
|
|||||||
|
|
||||||
# vuepress v2.x temp and cache directory
|
# vuepress v2.x temp and cache directory
|
||||||
.temp
|
.temp
|
||||||
.cache
|
|
||||||
|
|
||||||
# vitepress build output
|
|
||||||
**/.vitepress/dist
|
|
||||||
|
|
||||||
# vitepress cache directory
|
|
||||||
**/.vitepress/cache
|
|
||||||
|
|
||||||
# Docusaurus cache and generated files
|
# Docusaurus cache and generated files
|
||||||
.docusaurus
|
.docusaurus
|
||||||
@@ -136,3 +129,18 @@ dist
|
|||||||
.yarn/install-state.gz
|
.yarn/install-state.gz
|
||||||
.pnp.*
|
.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