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.
Files changed (59) hide show
  1. package/package.json +36 -0
  2. package/src/config.ts +25 -0
  3. package/src/dto/input/doctor.ts +120 -0
  4. package/src/dto/input/education.ts +11 -0
  5. package/src/dto/input/faq.ts +11 -0
  6. package/src/dto/input/index.ts +9 -0
  7. package/src/dto/input/language.ts +13 -0
  8. package/src/dto/input/location.ts +31 -0
  9. package/src/dto/input/patient.ts +17 -0
  10. package/src/dto/input/position.ts +21 -0
  11. package/src/dto/input/specialty.ts +40 -0
  12. package/src/dto/input/user.ts +87 -0
  13. package/src/dto/output/doctor.ts +90 -0
  14. package/src/dto/output/education.ts +11 -0
  15. package/src/dto/output/faq.ts +11 -0
  16. package/src/dto/output/index.ts +9 -0
  17. package/src/dto/output/language.ts +11 -0
  18. package/src/dto/output/location.ts +21 -0
  19. package/src/dto/output/patient.ts +49 -0
  20. package/src/dto/output/position.ts +15 -0
  21. package/src/dto/output/specialty.ts +53 -0
  22. package/src/dto/output/user.ts +57 -0
  23. package/src/enums/gender.ts +4 -0
  24. package/src/enums/index.ts +4 -0
  25. package/src/enums/language-levels.ts +9 -0
  26. package/src/enums/status-codes.ts +28 -0
  27. package/src/enums/user-role.ts +5 -0
  28. package/src/errors/BadRequestError.ts +18 -0
  29. package/src/errors/CustomError.ts +18 -0
  30. package/src/errors/NotFoundError.ts +18 -0
  31. package/src/errors/UnAuthorizedError.ts +18 -0
  32. package/src/errors/index.ts +4 -0
  33. package/src/index.ts +19 -0
  34. package/src/interfaces/LoggedInUserToken.ts +9 -0
  35. package/src/interfaces/index.ts +1 -0
  36. package/src/middleware/errorHandler.ts +31 -0
  37. package/src/middleware/index.ts +5 -0
  38. package/src/middleware/multer.ts +74 -0
  39. package/src/middleware/require-auth.ts +46 -0
  40. package/src/middleware/validate-request.ts +40 -0
  41. package/src/middleware/verify-roles.ts +17 -0
  42. package/src/models/base.ts +52 -0
  43. package/src/models/doctor.ts +96 -0
  44. package/src/models/education.ts +14 -0
  45. package/src/models/faq.ts +14 -0
  46. package/src/models/index.ts +10 -0
  47. package/src/models/language.ts +20 -0
  48. package/src/models/location.ts +24 -0
  49. package/src/models/patient.ts +35 -0
  50. package/src/models/position.ts +19 -0
  51. package/src/models/specialty.ts +37 -0
  52. package/src/models/user.ts +67 -0
  53. package/src/utils/index.ts +5 -0
  54. package/src/utils/orchestration-result.ts +74 -0
  55. package/src/utils/s3-helper.ts +72 -0
  56. package/src/utils/token-utils.ts +86 -0
  57. package/src/utils/validate-info.ts +26 -0
  58. package/src/utils/winston.ts +33 -0
  59. 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,9 @@
1
+ export * from "./doctor";
2
+ export * from "./education";
3
+ export * from "./faq";
4
+ export * from "./language";
5
+ export * from "./location";
6
+ export * from "./patient";
7
+ export * from "./position";
8
+ export * from "./specialty";
9
+ export * from "./user";
@@ -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,11 @@
1
+ import { IEducation } from "../../models";
2
+
3
+ export class EducationOutputDto {
4
+ year: number;
5
+ title: string;
6
+
7
+ constructor(edu: IEducation) {
8
+ this.year = edu.year;
9
+ this.title = edu.title;
10
+ }
11
+ }
@@ -0,0 +1,11 @@
1
+ import { IFaq } from "../../models";
2
+
3
+ export class FaqOutputDto {
4
+ title: string;
5
+ description: string;
6
+
7
+ constructor(f: IFaq) {
8
+ this.title = f.title;
9
+ this.description = f.description;
10
+ }
11
+ }
@@ -0,0 +1,9 @@
1
+ export * from "./doctor";
2
+ export * from "./education";
3
+ export * from "./faq";
4
+ export * from "./language";
5
+ export * from "./location";
6
+ export * from "./patient";
7
+ export * from "./position";
8
+ export * from "./specialty";
9
+ export * from "./user";
@@ -0,0 +1,11 @@
1
+ import { ILanguage } from "../../models";
2
+
3
+ export class LanguageOutputDto {
4
+ title: string;
5
+ level: string;
6
+
7
+ constructor(lang: ILanguage) {
8
+ this.title = lang.title;
9
+ this.level = lang.level;
10
+ }
11
+ }
@@ -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
+ }