docta-package 1.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +36 -0
- package/src/config.ts +25 -0
- package/src/dto/input/doctor.ts +120 -0
- package/src/dto/input/education.ts +11 -0
- package/src/dto/input/faq.ts +11 -0
- package/src/dto/input/index.ts +9 -0
- package/src/dto/input/language.ts +13 -0
- package/src/dto/input/location.ts +31 -0
- package/src/dto/input/patient.ts +17 -0
- package/src/dto/input/position.ts +21 -0
- package/src/dto/input/specialty.ts +40 -0
- package/src/dto/input/user.ts +87 -0
- package/src/dto/output/doctor.ts +90 -0
- package/src/dto/output/education.ts +11 -0
- package/src/dto/output/faq.ts +11 -0
- package/src/dto/output/index.ts +9 -0
- package/src/dto/output/language.ts +11 -0
- package/src/dto/output/location.ts +21 -0
- package/src/dto/output/patient.ts +49 -0
- package/src/dto/output/position.ts +15 -0
- package/src/dto/output/specialty.ts +53 -0
- package/src/dto/output/user.ts +57 -0
- package/src/enums/gender.ts +4 -0
- package/src/enums/index.ts +4 -0
- package/src/enums/language-levels.ts +9 -0
- package/src/enums/status-codes.ts +28 -0
- package/src/enums/user-role.ts +5 -0
- package/src/errors/BadRequestError.ts +18 -0
- package/src/errors/CustomError.ts +18 -0
- package/src/errors/NotFoundError.ts +18 -0
- package/src/errors/UnAuthorizedError.ts +18 -0
- package/src/errors/index.ts +4 -0
- package/src/index.ts +19 -0
- package/src/interfaces/LoggedInUserToken.ts +9 -0
- package/src/interfaces/index.ts +1 -0
- package/src/middleware/errorHandler.ts +31 -0
- package/src/middleware/index.ts +5 -0
- package/src/middleware/multer.ts +74 -0
- package/src/middleware/require-auth.ts +46 -0
- package/src/middleware/validate-request.ts +40 -0
- package/src/middleware/verify-roles.ts +17 -0
- package/src/models/base.ts +52 -0
- package/src/models/doctor.ts +96 -0
- package/src/models/education.ts +14 -0
- package/src/models/faq.ts +14 -0
- package/src/models/index.ts +10 -0
- package/src/models/language.ts +20 -0
- package/src/models/location.ts +24 -0
- package/src/models/patient.ts +35 -0
- package/src/models/position.ts +19 -0
- package/src/models/specialty.ts +37 -0
- package/src/models/user.ts +67 -0
- package/src/utils/index.ts +5 -0
- package/src/utils/orchestration-result.ts +74 -0
- package/src/utils/s3-helper.ts +72 -0
- package/src/utils/token-utils.ts +86 -0
- package/src/utils/validate-info.ts +26 -0
- package/src/utils/winston.ts +33 -0
- package/tsconfig.json +120 -0
package/package.json
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "docta-package",
|
|
3
|
+
"version": "1.0.1",
|
|
4
|
+
"description": "This package will contail all the required files to run the docta micro-service app",
|
|
5
|
+
"main": "index.js",
|
|
6
|
+
"types": "build/index.d.ts",
|
|
7
|
+
"scripts": {
|
|
8
|
+
"clean": "rm -rf build",
|
|
9
|
+
"build": "npm run clean && tsc",
|
|
10
|
+
"prepublishOnly": "npm run build",
|
|
11
|
+
"release": "npm version patch -m 'Release %s' && npm publish --access public"
|
|
12
|
+
},
|
|
13
|
+
"author": "",
|
|
14
|
+
"license": "ISC",
|
|
15
|
+
"devDependencies": {
|
|
16
|
+
"@types/bcryptjs": "^2.4.6",
|
|
17
|
+
"@types/express": "^5.0.3",
|
|
18
|
+
"@types/jest": "^30.0.0",
|
|
19
|
+
"@types/jsonwebtoken": "^9.0.10",
|
|
20
|
+
"@types/multer": "^2.0.0",
|
|
21
|
+
"@types/node": "^24.7.2",
|
|
22
|
+
"typescript": "^5.9.3"
|
|
23
|
+
},
|
|
24
|
+
"dependencies": {
|
|
25
|
+
"@aws-sdk/client-s3": "^3.908.0",
|
|
26
|
+
"@aws-sdk/s3-request-presigner": "^3.908.0",
|
|
27
|
+
"bcryptjs": "^3.0.2",
|
|
28
|
+
"class-transformer": "^0.5.1",
|
|
29
|
+
"class-validator": "^0.14.2",
|
|
30
|
+
"express": "^5.1.0",
|
|
31
|
+
"mongoose": "^8.19.1",
|
|
32
|
+
"reflect-metadata": "^0.2.2",
|
|
33
|
+
"winston": "^3.18.3",
|
|
34
|
+
"winston-daily-rotate-file": "^5.0.0"
|
|
35
|
+
}
|
|
36
|
+
}
|
package/src/config.ts
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
interface GeneralConfig {
|
|
2
|
+
accessTokenSecret: string;
|
|
3
|
+
refreshTokenSecret: string;
|
|
4
|
+
accessTokenExpiry: number;
|
|
5
|
+
refreshTokenExpiry: number;
|
|
6
|
+
awsAccessKey: string;
|
|
7
|
+
awsSecretKey: string;
|
|
8
|
+
awsS3Bucket: string;
|
|
9
|
+
awsS3Region: string;
|
|
10
|
+
forgotPasswordTokenSecret: string;
|
|
11
|
+
activationTokenSecret: string;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export const generalConfig: GeneralConfig = {
|
|
15
|
+
accessTokenSecret: String(process.env.ACCESS_TOKEN_SECRET),
|
|
16
|
+
refreshTokenSecret: String(process.env.REFRESH_TOKEN_SECRET),
|
|
17
|
+
accessTokenExpiry: Number(process.env.ACCESS_TOKEN_EXPIRY),
|
|
18
|
+
refreshTokenExpiry: Number(process.env.REFRESH_TOKEN_EXPIRY),
|
|
19
|
+
awsAccessKey: String(process.env.AWS_ACCESS_KEY),
|
|
20
|
+
awsSecretKey: String(process.env.AWS_SECRET_KEY),
|
|
21
|
+
awsS3Bucket: String(process.env.AWS_S3_BUCKET),
|
|
22
|
+
awsS3Region: String(process.env.AWS_S3_REGION),
|
|
23
|
+
forgotPasswordTokenSecret: String(process.env.FORGOT_PASSWORD_TOKEN_SECRET),
|
|
24
|
+
activationTokenSecret: String(process.env.ACTIVATION_TOKEN_SECRET),
|
|
25
|
+
};
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import {
|
|
2
|
+
IsEmail,
|
|
3
|
+
IsString,
|
|
4
|
+
MaxLength,
|
|
5
|
+
MinLength,
|
|
6
|
+
IsStrongPassword,
|
|
7
|
+
IsMongoId,
|
|
8
|
+
IsOptional,
|
|
9
|
+
IsNumber,
|
|
10
|
+
Min,
|
|
11
|
+
IsNotEmpty,
|
|
12
|
+
IsBoolean,
|
|
13
|
+
IsArray,
|
|
14
|
+
ValidateNested,
|
|
15
|
+
ArrayMaxSize,
|
|
16
|
+
} from "class-validator";
|
|
17
|
+
import { Type } from "class-transformer";
|
|
18
|
+
import { EducationInputDto } from ".";
|
|
19
|
+
import { PositionInputDto } from ".";
|
|
20
|
+
import { LanguageInputDto } from ".";
|
|
21
|
+
import { FaqInputDto } from ".";
|
|
22
|
+
import { LocationInputDto } from ".";
|
|
23
|
+
|
|
24
|
+
export class CreateDoctorDto {
|
|
25
|
+
@IsString()
|
|
26
|
+
@MinLength(3)
|
|
27
|
+
@MaxLength(50)
|
|
28
|
+
name: string;
|
|
29
|
+
|
|
30
|
+
@IsEmail()
|
|
31
|
+
email: string;
|
|
32
|
+
|
|
33
|
+
@IsMongoId()
|
|
34
|
+
specialtyId: string;
|
|
35
|
+
|
|
36
|
+
@IsOptional()
|
|
37
|
+
@IsString()
|
|
38
|
+
@MaxLength(500)
|
|
39
|
+
biography?: string;
|
|
40
|
+
|
|
41
|
+
@IsOptional()
|
|
42
|
+
@IsNumber()
|
|
43
|
+
@Min(0)
|
|
44
|
+
consultationFee?: number;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export class ActivateDoctorAccountDto {
|
|
48
|
+
@IsString()
|
|
49
|
+
@IsNotEmpty({ message: "Activation token token is required" })
|
|
50
|
+
token: string;
|
|
51
|
+
|
|
52
|
+
@IsString()
|
|
53
|
+
@MinLength(6)
|
|
54
|
+
@MaxLength(30)
|
|
55
|
+
@IsStrongPassword()
|
|
56
|
+
password: string;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export class UpdateDoctorDto {
|
|
60
|
+
@IsOptional()
|
|
61
|
+
@IsString()
|
|
62
|
+
@MinLength(3)
|
|
63
|
+
@MaxLength(50)
|
|
64
|
+
name: string;
|
|
65
|
+
|
|
66
|
+
@IsOptional()
|
|
67
|
+
@IsString()
|
|
68
|
+
@MaxLength(500)
|
|
69
|
+
biography?: string;
|
|
70
|
+
|
|
71
|
+
@IsOptional()
|
|
72
|
+
@IsNumber()
|
|
73
|
+
@Min(0)
|
|
74
|
+
consultationFee?: number;
|
|
75
|
+
|
|
76
|
+
@IsOptional()
|
|
77
|
+
@IsBoolean()
|
|
78
|
+
isVisible?: boolean;
|
|
79
|
+
|
|
80
|
+
// Replace-all educations array
|
|
81
|
+
@IsOptional()
|
|
82
|
+
@IsArray()
|
|
83
|
+
@ValidateNested({ each: true })
|
|
84
|
+
@Type(() => EducationInputDto)
|
|
85
|
+
educations?: EducationInputDto[];
|
|
86
|
+
|
|
87
|
+
// Replace-all positions array
|
|
88
|
+
@IsOptional()
|
|
89
|
+
@IsArray()
|
|
90
|
+
@ValidateNested({ each: true })
|
|
91
|
+
@Type(() => PositionInputDto)
|
|
92
|
+
positions?: PositionInputDto[];
|
|
93
|
+
|
|
94
|
+
// Replace-all languages array
|
|
95
|
+
@IsOptional()
|
|
96
|
+
@IsArray()
|
|
97
|
+
@ValidateNested({ each: true })
|
|
98
|
+
@Type(() => LanguageInputDto)
|
|
99
|
+
languages?: LanguageInputDto[];
|
|
100
|
+
|
|
101
|
+
// Replace-all FAQs array
|
|
102
|
+
@IsOptional()
|
|
103
|
+
@IsArray()
|
|
104
|
+
@ValidateNested({ each: true })
|
|
105
|
+
@Type(() => FaqInputDto)
|
|
106
|
+
faqs?: FaqInputDto[];
|
|
107
|
+
|
|
108
|
+
// Replace-all expertises array (array of strings)
|
|
109
|
+
@IsOptional()
|
|
110
|
+
@IsArray()
|
|
111
|
+
@ArrayMaxSize(5, { message: "A maximum of 5 expertises is allowed" })
|
|
112
|
+
@IsString({ each: true })
|
|
113
|
+
expertises?: string[];
|
|
114
|
+
|
|
115
|
+
// Optional location object (replace-all when provided)
|
|
116
|
+
@IsOptional()
|
|
117
|
+
@ValidateNested()
|
|
118
|
+
@Type(() => LocationInputDto)
|
|
119
|
+
location?: LocationInputDto;
|
|
120
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { IsNotEmpty, IsNumber, IsString, Min } from "class-validator";
|
|
2
|
+
|
|
3
|
+
export class EducationInputDto {
|
|
4
|
+
@IsNumber({}, { message: "Year must be a number" })
|
|
5
|
+
@Min(0, { message: "Year must be greater than or equal to 0" })
|
|
6
|
+
year!: number;
|
|
7
|
+
|
|
8
|
+
@IsString({ message: "Title must be a string" })
|
|
9
|
+
@IsNotEmpty({ message: "Title is required" })
|
|
10
|
+
title!: string;
|
|
11
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { IsNotEmpty, IsString } from "class-validator";
|
|
2
|
+
|
|
3
|
+
export class FaqInputDto {
|
|
4
|
+
@IsString({ message: "Title must be a string" })
|
|
5
|
+
@IsNotEmpty({ message: "Title is required" })
|
|
6
|
+
title!: string;
|
|
7
|
+
|
|
8
|
+
@IsString({ message: "Description must be a string" })
|
|
9
|
+
@IsNotEmpty({ message: "Description is required" })
|
|
10
|
+
description!: string;
|
|
11
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { IsEnum, IsNotEmpty, IsString } from "class-validator";
|
|
2
|
+
import { EnumLanguageLevel } from "../../enums";
|
|
3
|
+
|
|
4
|
+
export class LanguageInputDto {
|
|
5
|
+
@IsString({ message: "Title must be a string" })
|
|
6
|
+
@IsNotEmpty({ message: "Title is required" })
|
|
7
|
+
title!: string;
|
|
8
|
+
|
|
9
|
+
@IsEnum(EnumLanguageLevel, {
|
|
10
|
+
message: "Level must be a valid language level",
|
|
11
|
+
})
|
|
12
|
+
level!: EnumLanguageLevel;
|
|
13
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { IsNumber, IsOptional, IsString } from "class-validator";
|
|
2
|
+
|
|
3
|
+
export class LocationInputDto {
|
|
4
|
+
@IsOptional()
|
|
5
|
+
@IsString()
|
|
6
|
+
address?: string;
|
|
7
|
+
|
|
8
|
+
@IsOptional()
|
|
9
|
+
@IsString()
|
|
10
|
+
city?: string;
|
|
11
|
+
|
|
12
|
+
@IsOptional()
|
|
13
|
+
@IsString()
|
|
14
|
+
country?: string; // ISO code eg: fr
|
|
15
|
+
|
|
16
|
+
@IsOptional()
|
|
17
|
+
@IsNumber()
|
|
18
|
+
lat?: number;
|
|
19
|
+
|
|
20
|
+
@IsOptional()
|
|
21
|
+
@IsNumber()
|
|
22
|
+
lng?: number;
|
|
23
|
+
|
|
24
|
+
@IsOptional()
|
|
25
|
+
@IsString()
|
|
26
|
+
zipcode?: string;
|
|
27
|
+
|
|
28
|
+
@IsOptional()
|
|
29
|
+
@IsNumber()
|
|
30
|
+
distanceInMeters?: number | null;
|
|
31
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { IsEnum, IsNumber, IsOptional, IsString } from "class-validator";
|
|
2
|
+
import { Gender } from "../../enums";
|
|
3
|
+
|
|
4
|
+
export class UpdatePatientDto {
|
|
5
|
+
@IsOptional()
|
|
6
|
+
@IsString()
|
|
7
|
+
@IsEnum(Gender)
|
|
8
|
+
gender?: Gender;
|
|
9
|
+
|
|
10
|
+
@IsOptional()
|
|
11
|
+
@IsString()
|
|
12
|
+
phoneNumber?: string;
|
|
13
|
+
|
|
14
|
+
@IsOptional()
|
|
15
|
+
@IsNumber()
|
|
16
|
+
dob?: number;
|
|
17
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { IsNotEmpty, IsNumber, IsOptional, IsString, Min } from "class-validator";
|
|
2
|
+
|
|
3
|
+
export class PositionInputDto {
|
|
4
|
+
@IsNumber({}, { message: "Start date must be a number (timestamp in ms)" })
|
|
5
|
+
@Min(0, { message: "Start date must be >= 0" })
|
|
6
|
+
startDate!: number;
|
|
7
|
+
|
|
8
|
+
@IsOptional()
|
|
9
|
+
@IsNumber({}, { message: "End date must be a number (timestamp in ms)" })
|
|
10
|
+
@Min(0, { message: "End date must be >= 0" })
|
|
11
|
+
endDate?: number;
|
|
12
|
+
|
|
13
|
+
@IsString({ message: "Title must be a string" })
|
|
14
|
+
@IsNotEmpty({ message: "Title is required" })
|
|
15
|
+
title!: string;
|
|
16
|
+
|
|
17
|
+
@IsString({ message: "Company must be a string" })
|
|
18
|
+
@IsNotEmpty({ message: "Company is required" })
|
|
19
|
+
company!: string;
|
|
20
|
+
}
|
|
21
|
+
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import {
|
|
2
|
+
IsNotEmpty,
|
|
3
|
+
IsOptional,
|
|
4
|
+
IsString,
|
|
5
|
+
ValidateNested,
|
|
6
|
+
} from "class-validator";
|
|
7
|
+
import { Type } from "class-transformer";
|
|
8
|
+
|
|
9
|
+
export class LocalizedSpecialtyDto {
|
|
10
|
+
@IsString()
|
|
11
|
+
@IsNotEmpty()
|
|
12
|
+
name!: string;
|
|
13
|
+
|
|
14
|
+
@IsOptional()
|
|
15
|
+
@IsString()
|
|
16
|
+
description?: string | null;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export class CreateSpecialtyDto {
|
|
20
|
+
@ValidateNested()
|
|
21
|
+
@Type(() => LocalizedSpecialtyDto)
|
|
22
|
+
en!: LocalizedSpecialtyDto;
|
|
23
|
+
|
|
24
|
+
@IsOptional()
|
|
25
|
+
@ValidateNested()
|
|
26
|
+
@Type(() => LocalizedSpecialtyDto)
|
|
27
|
+
fr?: LocalizedSpecialtyDto;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export class UpdateSpecialtyDto {
|
|
31
|
+
@IsOptional()
|
|
32
|
+
@ValidateNested()
|
|
33
|
+
@Type(() => LocalizedSpecialtyDto)
|
|
34
|
+
en?: LocalizedSpecialtyDto;
|
|
35
|
+
|
|
36
|
+
@IsOptional()
|
|
37
|
+
@ValidateNested()
|
|
38
|
+
@Type(() => LocalizedSpecialtyDto)
|
|
39
|
+
fr?: LocalizedSpecialtyDto;
|
|
40
|
+
}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import {
|
|
2
|
+
IsEmail,
|
|
3
|
+
IsNotEmpty,
|
|
4
|
+
IsOptional,
|
|
5
|
+
IsString,
|
|
6
|
+
IsStrongPassword,
|
|
7
|
+
MaxLength,
|
|
8
|
+
MinLength,
|
|
9
|
+
} from "class-validator";
|
|
10
|
+
|
|
11
|
+
export class CreateUserDto {
|
|
12
|
+
@IsString()
|
|
13
|
+
@MinLength(3)
|
|
14
|
+
@MaxLength(50)
|
|
15
|
+
name: string;
|
|
16
|
+
|
|
17
|
+
@IsEmail()
|
|
18
|
+
email: string;
|
|
19
|
+
|
|
20
|
+
@IsString()
|
|
21
|
+
@MinLength(6)
|
|
22
|
+
@MaxLength(30)
|
|
23
|
+
@IsStrongPassword()
|
|
24
|
+
password: string;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export class LoginDto {
|
|
28
|
+
@IsEmail({}, { message: "Please enter a valid email address" })
|
|
29
|
+
@IsNotEmpty({ message: "Email is required" })
|
|
30
|
+
email: string;
|
|
31
|
+
|
|
32
|
+
@IsString({ message: "Password must be a string" })
|
|
33
|
+
@IsNotEmpty({ message: "Password is required" })
|
|
34
|
+
password: string;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export class RefreshTokenDto {
|
|
38
|
+
@IsString({ message: "Refresh token must be a string" })
|
|
39
|
+
@IsNotEmpty({ message: "Refresh token is required" })
|
|
40
|
+
refreshToken: string;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export class ActivateAccountDto {
|
|
44
|
+
@IsString({ message: "Token must be a string" })
|
|
45
|
+
@IsNotEmpty({ message: "Token is required" })
|
|
46
|
+
token: string;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export class ForgotPasswordDto {
|
|
50
|
+
@IsEmail({}, { message: "Please enter a valid email address" })
|
|
51
|
+
@IsNotEmpty({ message: "Email is required" })
|
|
52
|
+
email: string;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export class ResetPasswordDto {
|
|
56
|
+
@IsString({ message: "Token must be a string" })
|
|
57
|
+
@IsNotEmpty({ message: "Token is required" })
|
|
58
|
+
token: string;
|
|
59
|
+
|
|
60
|
+
@IsString()
|
|
61
|
+
@MinLength(6)
|
|
62
|
+
@MaxLength(30)
|
|
63
|
+
@IsStrongPassword()
|
|
64
|
+
password: string;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export class UpdateUserDto {
|
|
68
|
+
@IsOptional()
|
|
69
|
+
@IsString()
|
|
70
|
+
@MinLength(3)
|
|
71
|
+
@MaxLength(50)
|
|
72
|
+
name: string;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export class UpdatePasswordDto {
|
|
76
|
+
@IsString()
|
|
77
|
+
@MinLength(6)
|
|
78
|
+
@MaxLength(30)
|
|
79
|
+
@IsStrongPassword()
|
|
80
|
+
oldPassword: string;
|
|
81
|
+
|
|
82
|
+
@IsString()
|
|
83
|
+
@MinLength(6)
|
|
84
|
+
@MaxLength(30)
|
|
85
|
+
@IsStrongPassword()
|
|
86
|
+
newPassword: string;
|
|
87
|
+
}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import { IDoctorDocument } from "../../models";
|
|
2
|
+
import { SpecialtyOutputDto } from ".";
|
|
3
|
+
import { UserOutputDto } from ".";
|
|
4
|
+
import { EducationOutputDto } from ".";
|
|
5
|
+
import { PositionOutputDto } from ".";
|
|
6
|
+
import { LanguageOutputDto } from ".";
|
|
7
|
+
import { FaqOutputDto } from ".";
|
|
8
|
+
import { LocationOutputDto } from ".";
|
|
9
|
+
|
|
10
|
+
// Base DTO for everyone
|
|
11
|
+
export class DoctorOutputDto {
|
|
12
|
+
id: string;
|
|
13
|
+
user: UserOutputDto;
|
|
14
|
+
specialty: SpecialtyOutputDto;
|
|
15
|
+
name: string;
|
|
16
|
+
biography: string | null;
|
|
17
|
+
slug: string;
|
|
18
|
+
isActive: boolean;
|
|
19
|
+
consultationFee: number | null;
|
|
20
|
+
isVerified: boolean;
|
|
21
|
+
isVisible: boolean;
|
|
22
|
+
photo: string | null;
|
|
23
|
+
educations: EducationOutputDto[];
|
|
24
|
+
positions: PositionOutputDto[];
|
|
25
|
+
languages: LanguageOutputDto[];
|
|
26
|
+
faqs: FaqOutputDto[];
|
|
27
|
+
expertises: string[];
|
|
28
|
+
location: LocationOutputDto | null;
|
|
29
|
+
|
|
30
|
+
isDeleted: boolean;
|
|
31
|
+
createdAt: number;
|
|
32
|
+
updatedAt: number;
|
|
33
|
+
|
|
34
|
+
constructor(doctor: IDoctorDocument) {
|
|
35
|
+
this.id = doctor.id.toString();
|
|
36
|
+
this.user = new UserOutputDto(doctor.user);
|
|
37
|
+
this.name = doctor.name;
|
|
38
|
+
this.specialty = new SpecialtyOutputDto(doctor.specialty);
|
|
39
|
+
this.slug = doctor.slug;
|
|
40
|
+
this.biography = doctor.biography || null;
|
|
41
|
+
this.isActive = doctor.isActive;
|
|
42
|
+
this.consultationFee = doctor.consultationFee ?? null;
|
|
43
|
+
this.isVerified = doctor.isVerified;
|
|
44
|
+
this.isVisible = doctor.isVisible;
|
|
45
|
+
this.photo = doctor.photo || null;
|
|
46
|
+
this.educations = (doctor.educations || [])
|
|
47
|
+
.map((e) => new EducationOutputDto(e))
|
|
48
|
+
.sort((a, b) => b.year - a.year);
|
|
49
|
+
this.positions = (doctor.positions || [])
|
|
50
|
+
.map((p) => new PositionOutputDto(p))
|
|
51
|
+
.sort((a, b) => b.startDate - a.startDate);
|
|
52
|
+
this.languages = (doctor.languages || []).map(
|
|
53
|
+
(l) => new LanguageOutputDto(l)
|
|
54
|
+
);
|
|
55
|
+
this.faqs = (doctor.faqs || []).map((f) => new FaqOutputDto(f));
|
|
56
|
+
this.expertises = doctor.expertises || [];
|
|
57
|
+
this.location = doctor.location
|
|
58
|
+
? new LocationOutputDto(doctor.location)
|
|
59
|
+
: null;
|
|
60
|
+
this.isDeleted = doctor.isDeleted;
|
|
61
|
+
this.createdAt = doctor.createdAt;
|
|
62
|
+
this.updatedAt = doctor.updatedAt;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// Extended DTO for admin responses
|
|
67
|
+
export class DoctorAdminOutputDto extends DoctorOutputDto {
|
|
68
|
+
isDeactivatedByAdmin: boolean | null;
|
|
69
|
+
createdBy: UserOutputDto | null;
|
|
70
|
+
updatedBy: UserOutputDto | null;
|
|
71
|
+
deletedBy: UserOutputDto | null;
|
|
72
|
+
|
|
73
|
+
constructor(doctor: IDoctorDocument) {
|
|
74
|
+
super(doctor); // call base constructor
|
|
75
|
+
|
|
76
|
+
this.isDeactivatedByAdmin = doctor.isDeactivatedByAdmin ?? null;
|
|
77
|
+
|
|
78
|
+
this.createdBy = doctor.createdBy
|
|
79
|
+
? new UserOutputDto(doctor.createdBy)
|
|
80
|
+
: null;
|
|
81
|
+
|
|
82
|
+
this.updatedBy = doctor.updatedBy
|
|
83
|
+
? new UserOutputDto(doctor.updatedBy)
|
|
84
|
+
: null;
|
|
85
|
+
|
|
86
|
+
this.deletedBy = doctor.deletedBy
|
|
87
|
+
? new UserOutputDto(doctor.deletedBy)
|
|
88
|
+
: null;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { ILocation } from "../../models";
|
|
2
|
+
|
|
3
|
+
export class LocationOutputDto {
|
|
4
|
+
address: string | null;
|
|
5
|
+
city: string | null;
|
|
6
|
+
country: string | null;
|
|
7
|
+
lat: number | null;
|
|
8
|
+
lng: number | null;
|
|
9
|
+
zipcode: string | null;
|
|
10
|
+
distanceInMeters: number | null;
|
|
11
|
+
|
|
12
|
+
constructor(loc: ILocation) {
|
|
13
|
+
this.address = loc.address ?? null;
|
|
14
|
+
this.city = loc.city ?? null;
|
|
15
|
+
this.country = loc.country ?? null;
|
|
16
|
+
this.lat = loc.lat ?? null;
|
|
17
|
+
this.lng = loc.lng ?? null;
|
|
18
|
+
this.zipcode = loc.zipcode ?? null;
|
|
19
|
+
this.distanceInMeters = loc.distanceInMeters ?? null;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { Gender } from "../../enums";
|
|
2
|
+
import { IPatientDocument } from "../../models";
|
|
3
|
+
import { UserOutputDto } from ".";
|
|
4
|
+
|
|
5
|
+
// Base DTO for everyone
|
|
6
|
+
export class PatientOutputDto {
|
|
7
|
+
id: string;
|
|
8
|
+
user: UserOutputDto;
|
|
9
|
+
dob: number | null;
|
|
10
|
+
phoneNumber: string | null;
|
|
11
|
+
gender: Gender | null;
|
|
12
|
+
isDeleted: boolean;
|
|
13
|
+
createdAt: number;
|
|
14
|
+
updatedAt: number;
|
|
15
|
+
|
|
16
|
+
constructor(patient: IPatientDocument) {
|
|
17
|
+
this.id = patient.id.toString();
|
|
18
|
+
this.user = new UserOutputDto(patient.user);
|
|
19
|
+
this.dob = patient.dob || null;
|
|
20
|
+
this.phoneNumber = patient.phoneNumber || null;
|
|
21
|
+
this.gender = patient.gender || null;
|
|
22
|
+
this.isDeleted = patient.isDeleted;
|
|
23
|
+
this.createdAt = patient.createdAt;
|
|
24
|
+
this.updatedAt = patient.updatedAt;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// Extended DTO for admin responses
|
|
29
|
+
export class PatientAdminOutputDto extends PatientOutputDto {
|
|
30
|
+
createdBy: UserOutputDto | null;
|
|
31
|
+
updatedBy: UserOutputDto | null;
|
|
32
|
+
deletedBy: UserOutputDto | null;
|
|
33
|
+
|
|
34
|
+
constructor(patient: IPatientDocument) {
|
|
35
|
+
super(patient); // call base constructor
|
|
36
|
+
|
|
37
|
+
this.createdBy = patient.createdBy
|
|
38
|
+
? new UserOutputDto(patient.createdBy)
|
|
39
|
+
: null;
|
|
40
|
+
|
|
41
|
+
this.updatedBy = patient.updatedBy
|
|
42
|
+
? new UserOutputDto(patient.updatedBy)
|
|
43
|
+
: null;
|
|
44
|
+
|
|
45
|
+
this.deletedBy = patient.deletedBy
|
|
46
|
+
? new UserOutputDto(patient.deletedBy)
|
|
47
|
+
: null;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { IPosition } from "../../models";
|
|
2
|
+
|
|
3
|
+
export class PositionOutputDto {
|
|
4
|
+
startDate: number;
|
|
5
|
+
endDate: number | null;
|
|
6
|
+
title: string;
|
|
7
|
+
company: string;
|
|
8
|
+
|
|
9
|
+
constructor(pos: IPosition) {
|
|
10
|
+
this.startDate = pos.startDate;
|
|
11
|
+
this.endDate = pos.endDate ?? null;
|
|
12
|
+
this.title = pos.title;
|
|
13
|
+
this.company = pos.company;
|
|
14
|
+
}
|
|
15
|
+
}
|