@duvdu-v1/duvdu 1.1.354 → 1.1.359

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 (55) hide show
  1. package/build/errors/pdf-generation-error.d.ts +9 -0
  2. package/build/errors/pdf-generation-error.js +15 -0
  3. package/build/guards/isEmailVerified.guard.d.ts +2 -0
  4. package/build/guards/isEmailVerified.guard.js +15 -0
  5. package/build/guards/isPhoneNumberVerified.guard.d.ts +2 -0
  6. package/build/guards/isPhoneNumberVerified.guard.js +15 -0
  7. package/build/index.d.ts +6 -0
  8. package/build/index.js +6 -0
  9. package/build/mailer/mailer.interface.d.ts +68 -0
  10. package/build/mailer/mailer.interface.js +12 -0
  11. package/build/mailer/mailer.service.d.ts +10 -0
  12. package/build/mailer/mailer.service.js +149 -0
  13. package/build/mailer/strategies/resend.strategy.d.ts +6 -0
  14. package/build/mailer/strategies/resend.strategy.js +48 -0
  15. package/build/mailer/strategies/smtp.strategy.d.ts +6 -0
  16. package/build/mailer/strategies/smtp.strategy.js +50 -0
  17. package/build/mailer/templates/layouts/main.hbs +103 -0
  18. package/build/mailer/templates/partials/footer.hbs +4 -0
  19. package/build/mailer/templates/partials/header.hbs +4 -0
  20. package/build/mailer/templates/views/account-update.hbs +20 -0
  21. package/build/mailer/templates/views/complaint-escalation.hbs +23 -0
  22. package/build/mailer/templates/views/invoice.hbs +29 -0
  23. package/build/mailer/templates/views/new-user.hbs +23 -0
  24. package/build/mailer/templates/views/organization-user-created.hbs +25 -0
  25. package/build/mailer/templates/views/reset-password.hbs +14 -0
  26. package/build/mailer/templates/views/verify-email.hbs +14 -0
  27. package/build/mailer/templates/views/welcome.hbs +22 -0
  28. package/build/middlewares/auth.middleware.js +2 -1
  29. package/build/middlewares/optional-auth.middleware.js +2 -1
  30. package/build/models/User.model.d.ts +2 -2
  31. package/build/models/User.model.js +15 -6
  32. package/build/models/all-contracts.model.js +1 -1
  33. package/build/models/contracts.model.d.ts +7 -0
  34. package/build/models/contracts.model.js +7 -0
  35. package/build/models/settings.model.d.ts +6 -0
  36. package/build/models/settings.model.js +6 -0
  37. package/build/services/pdf-templates/layouts/main.hbs +91 -0
  38. package/build/services/pdf-templates/partials/footer.hbs +4 -0
  39. package/build/services/pdf-templates/partials/header.hbs +4 -0
  40. package/build/services/pdf-templates/views/invoice.hbs +60 -0
  41. package/build/services/pdfGenerator.service.d.ts +16 -0
  42. package/build/services/pdfGenerator.service.js +198 -0
  43. package/build/services/rank.service.d.ts +2 -2
  44. package/build/types/JwtPayload.d.ts +2 -1
  45. package/build/types/User.d.ts +18 -7
  46. package/build/types/User.js +4 -0
  47. package/build/types/model-names.d.ts +1 -0
  48. package/build/types/model-names.js +1 -0
  49. package/build/types/pdf.types.d.ts +30 -0
  50. package/build/types/pdf.types.js +7 -0
  51. package/build/types/systemRoles.d.ts +1 -2
  52. package/build/types/systemRoles.js +3 -2
  53. package/build/utils/mask.d.ts +5 -0
  54. package/build/utils/mask.js +49 -0
  55. package/package.json +10 -2
@@ -0,0 +1,29 @@
1
+ <h2>Your Duvdu invoice</h2>
2
+
3
+ <p>Hello {{#if name}}{{name}}{{else}}there{{/if}},</p>
4
+
5
+ <p>Thank you for your payment. Your invoice for the transaction below is attached as a PDF.</p>
6
+
7
+ <table style="border-collapse: collapse;">
8
+ <tr>
9
+ <td style="padding: 4px 12px 4px 0;"><strong>Transaction ID:</strong></td>
10
+ <td style="padding: 4px 0;">{{transactionId}}</td>
11
+ </tr>
12
+ <tr>
13
+ <td style="padding: 4px 12px 4px 0;"><strong>Date:</strong></td>
14
+ <td style="padding: 4px 0;">{{date}}</td>
15
+ </tr>
16
+ <tr>
17
+ <td style="padding: 4px 12px 4px 0;"><strong>Amount:</strong></td>
18
+ <td style="padding: 4px 0;">{{amount}} {{currency}}</td>
19
+ </tr>
20
+ <tr>
21
+ <td style="padding: 4px 12px 4px 0;"><strong>Service:</strong></td>
22
+ <td style="padding: 4px 0;">{{serviceLabel}}</td>
23
+ </tr>
24
+ </table>
25
+
26
+ <p>If you have any questions about this invoice, please contact our support team.</p>
27
+
28
+ <p>Best regards,<br>
29
+ The Duvdu Team</p>
@@ -0,0 +1,23 @@
1
+ <h2>Your Duvdu account is ready</h2>
2
+
3
+ <p>Hello {{#if user}}{{user}}{{else}}there{{/if}},</p>
4
+
5
+ <p>An account has been created for you on Duvdu by an administrator.</p>
6
+
7
+ <p>Your role: <strong>{{role}}</strong></p>
8
+
9
+ {{#if email}}
10
+ <p>Email: <strong>{{email}}</strong></p>
11
+ {{/if}}
12
+
13
+ {{#if tempPassword}}
14
+ <p>Temporary password: <strong>{{tempPassword}}</strong></p>
15
+ <p>For your security, please change your password after your first login.</p>
16
+ {{/if}}
17
+
18
+ {{#if verificationLink}}
19
+ <p><a href="{{verificationLink}}" class="button">Log in to Duvdu</a></p>
20
+ {{/if}}
21
+
22
+ <p>Best regards,<br>
23
+ The Duvdu Team</p>
@@ -0,0 +1,25 @@
1
+ <h2>Welcome to Duvdu</h2>
2
+
3
+ <p>Hello {{name}},</p>
4
+
5
+ <p>An account has been created for you on Duvdu.</p>
6
+
7
+ <p>Here are your login credentials:</p>
8
+ <ul>
9
+ <li><strong>Email:</strong> {{email}}</li>
10
+ {{#if password}}
11
+ <li><strong>Password:</strong> {{password}}</li>
12
+ {{/if}}
13
+ </ul>
14
+
15
+ <p>Click the button below to verify your email and log in:</p>
16
+
17
+ <p><a href="{{verificationLink}}" class="button">Verify Email & Log In</a></p>
18
+
19
+ <p>If the button doesn't work, copy and paste this link into your browser:</p>
20
+ <p>{{verificationLink}}</p>
21
+
22
+ <p>We recommend changing your password after your first login.</p>
23
+
24
+ <p>Best regards,<br>
25
+ The Duvdu Team</p>
@@ -0,0 +1,14 @@
1
+ <h2>Reset your password</h2>
2
+
3
+ <p>Hello {{name}},</p>
4
+
5
+ <p>We received a request to reset the password for your Duvdu account. Use the code below to continue:</p>
6
+
7
+ <h3 style="color: #6C5CE7; background-color: #f5f3ff; padding: 14px 20px; width: fit-content; border-radius: 8px; letter-spacing: 4px; font-size: 22px;">{{verificationCode}}</h3>
8
+
9
+ <p><strong>This code expires in {{expiryTime}}.</strong></p>
10
+
11
+ <p>If you didn't request a password reset, please ignore this email or contact our support team if you have any concerns. For your security, this code can only be used once.</p>
12
+
13
+ <p>Best regards,<br>
14
+ The Duvdu Security Team</p>
@@ -0,0 +1,14 @@
1
+ <h2>Verify your email</h2>
2
+
3
+ <p>Hello {{name}},</p>
4
+
5
+ <p>Welcome to Duvdu! Use the verification code below to confirm your email address:</p>
6
+
7
+ <h3 style="color: #6C5CE7; background-color: #f5f3ff; padding: 14px 20px; width: fit-content; border-radius: 8px; letter-spacing: 4px; font-size: 22px;">{{verificationCode}}</h3>
8
+
9
+ <p><strong>This code expires in {{expiryTime}} minutes.</strong></p>
10
+
11
+ <p>If you didn't create a Duvdu account, you can safely ignore this email.</p>
12
+
13
+ <p>Best regards,<br>
14
+ The Duvdu Team</p>
@@ -0,0 +1,22 @@
1
+ <h2>Welcome to Duvdu, {{name}}!</h2>
2
+
3
+ <p>Thanks for joining Duvdu — the platform built for creatives, clients, and studios to collaborate and grow together.</p>
4
+
5
+ {{#if features}}
6
+ <p>Here are a few things you can do to get started:</p>
7
+ <ul>
8
+ {{#each features}}
9
+ <li>{{this}}</li>
10
+ {{/each}}
11
+ </ul>
12
+ {{/if}}
13
+
14
+ {{#if verificationLink}}
15
+ <p>Please verify your email address to unlock your account:</p>
16
+ <p><a href="{{verificationLink}}" class="button">Verify Email</a></p>
17
+ {{/if}}
18
+
19
+ <p>If you ever need a hand, our support team is just a message away.</p>
20
+
21
+ <p>Welcome aboard,<br>
22
+ The Duvdu Team</p>
@@ -95,7 +95,8 @@ const isauthenticated = (req, res, next) => __awaiter(void 0, void 0, void 0, fu
95
95
  return res.status(423).json({ message: 'invalid role' });
96
96
  const accessToken = (0, exports.generateAccessToken)({
97
97
  id: user.id,
98
- isVerified: user.isVerified,
98
+ isEmailVerified: user.isEmailVerified,
99
+ isPhoneNumberVerified: user.isPhoneNumberVerified,
99
100
  isBlocked: user.isBlocked,
100
101
  role: { key: role.key, permissions: role.permissions },
101
102
  });
@@ -60,7 +60,8 @@ const optionalAuthenticated = (req, res, next) => __awaiter(void 0, void 0, void
60
60
  return res.status(423).json({ message: 'invalid role' });
61
61
  const accessToken = (0, auth_middleware_1.generateAccessToken)({
62
62
  id: user.id,
63
- isVerified: user.isVerified,
63
+ isEmailVerified: user.isEmailVerified,
64
+ isPhoneNumberVerified: user.isPhoneNumberVerified,
64
65
  isBlocked: user.isBlocked,
65
66
  role: { key: role.key, permissions: role.permissions },
66
67
  });
@@ -1,6 +1,6 @@
1
1
  import { Iuser } from '../types/User';
2
- export declare const Users: import("mongoose").Model<Iuser, {}, {}, {}, import("mongoose").Document<unknown, {}, Iuser, {}, {}> & Iuser & {
2
+ export declare const Users: import("mongoose").Model<Iuser, {}, {}, {}, import("mongoose").Document<unknown, {}, Iuser, {}, {}> & Iuser & Required<{
3
3
  _id: import("mongoose").Types.ObjectId;
4
- } & {
4
+ }> & {
5
5
  __v: number;
6
6
  }, any>;
@@ -16,17 +16,23 @@ const model_names_1 = require("../types/model-names");
16
16
  const userSchema = new mongoose_1.Schema({
17
17
  googleId: { type: String, default: null },
18
18
  appleId: { type: String, default: null },
19
- email: { type: String, unique: true, sparse: true },
19
+ email: { type: String },
20
20
  name: { type: String, default: null },
21
21
  isDeleted: { type: Boolean, default: false },
22
22
  phoneNumber: {
23
23
  key: { type: String, default: null },
24
- number: { type: String, unique: true, sparse: true },
24
+ number: { type: String },
25
25
  },
26
- username: { type: String, unique: true, sparse: true },
26
+ username: { type: String },
27
27
  password: String,
28
- verificationCode: { code: String, expireAt: Date, reason: { type: String, default: null } },
29
- isVerified: { type: Boolean, default: false },
28
+ isEmailVerified: { type: Boolean, default: false },
29
+ isPhoneNumberVerified: { type: Boolean, default: false },
30
+ verificationCode: new mongoose_1.Schema({
31
+ expireAt: { type: Date, required: true, default: null },
32
+ reason: { type: String, required: true, default: null },
33
+ code: { type: String, required: true, default: null },
34
+ isVerified: { type: Boolean, default: false },
35
+ }, { timestamps: true }),
30
36
  refreshTokens: [
31
37
  {
32
38
  token: { type: String, default: null },
@@ -87,7 +93,10 @@ const userSchema = new mongoose_1.Schema({
87
93
  },
88
94
  })
89
95
  .index({ name: 'text' })
90
- .index({ location: '2dsphere' });
96
+ .index({ location: '2dsphere' })
97
+ .index({ email: 1 }, { unique: true, partialFilterExpression: { isEmailVerified: true } })
98
+ .index({ 'phoneNumber.number': 1 }, { unique: true, partialFilterExpression: { isPhoneNumberVerified: true } })
99
+ .index({ username: 1 }, { unique: true });
91
100
  userSchema.pre('save', function (next) {
92
101
  return __awaiter(this, void 0, void 0, function* () {
93
102
  if (this.isModified('acceptedProjectsCounter') ||
@@ -51,4 +51,4 @@ exports.ContractReports = (0, mongoose_1.model)(model_names_1.MODELS.contractRep
51
51
  ref: model_names_1.MODELS.user,
52
52
  default: null,
53
53
  },
54
- }, { collection: model_names_1.MODELS.contractReports, timestamps: true }).index({ reporter: 1, contract: 1 }, { unique: true }));
54
+ }, { collection: model_names_1.MODELS.contractReports, timestamps: true }));
@@ -26,6 +26,13 @@ export interface Icontract {
26
26
  deadline: Date;
27
27
  status: ContractStatus;
28
28
  submitedAt: Date;
29
+ taxConfig: {
30
+ vat: number;
31
+ transactionFee: number;
32
+ flatTransactionFee: number;
33
+ xFactor: number;
34
+ };
35
+ totalVat: number;
29
36
  createdAt: Date;
30
37
  updatedAt: Date;
31
38
  }
@@ -34,4 +34,11 @@ exports.Contracts = (0, mongoose_1.model)(model_names_1.MODELS.contracts, new mo
34
34
  default: ContractStatus.pending,
35
35
  },
36
36
  submitedAt: Date,
37
+ taxConfig: {
38
+ vat: { type: Number, default: 0 },
39
+ transactionFee: { type: Number, default: 0 },
40
+ flatTransactionFee: { type: Number, default: 0 },
41
+ xFactor: { type: Number, default: 1 },
42
+ },
43
+ totalVat: { type: Number, default: 0 },
37
44
  }, { timestamps: true, collection: model_names_1.MODELS.contracts }));
@@ -10,6 +10,12 @@ export interface Isetting {
10
10
  title: string;
11
11
  subTitle: string;
12
12
  }[];
13
+ taxConfig: {
14
+ vat: number;
15
+ transactionFee: number;
16
+ flatTransactionFee: number;
17
+ xFactor: number;
18
+ };
13
19
  }
14
20
  export declare const Setting: import("mongoose").Model<Isetting, {}, {}, {}, import("mongoose").Document<unknown, {}, Isetting, {}, {}> & Isetting & {
15
21
  _id: import("mongoose").Types.ObjectId;
@@ -15,6 +15,12 @@ exports.Setting = (0, mongoose_1.model)(model_names_1.MODELS.setting, new mongoo
15
15
  subTitle: { type: String, default: null },
16
16
  },
17
17
  ],
18
+ taxConfig: {
19
+ vat: { type: Number, default: 0 },
20
+ transactionFee: { type: Number, default: 0 },
21
+ flatTransactionFee: { type: Number, default: 0 },
22
+ xFactor: { type: Number, default: 1 },
23
+ },
18
24
  }, { timestamps: true, collection: model_names_1.MODELS.setting, toJSON: { transform: (doc, ret) => {
19
25
  if (ret.default_profile) {
20
26
  ret.default_profile = `${process.env.BUCKET_HOST}/${ret.default_profile}`;
@@ -0,0 +1,91 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <title>{{documentTitle}}</title>
6
+ <style>
7
+ @page { size: A4; margin: 0; }
8
+ * { box-sizing: border-box; }
9
+ body {
10
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
11
+ color: #1f2937;
12
+ margin: 0;
13
+ padding: 0;
14
+ background-color: #f5f3ff;
15
+ -webkit-print-color-adjust: exact;
16
+ print-color-adjust: exact;
17
+ }
18
+ .page {
19
+ background-color: #ffffff;
20
+ width: 100%;
21
+ min-height: 100vh;
22
+ padding: 48px 56px;
23
+ }
24
+ .brand {
25
+ text-align: center;
26
+ padding-bottom: 20px;
27
+ margin-bottom: 32px;
28
+ border-bottom: 1px solid #ede9fe;
29
+ }
30
+ .brand h1 {
31
+ margin: 0;
32
+ font-size: 30px;
33
+ font-weight: 700;
34
+ letter-spacing: -0.5px;
35
+ background: linear-gradient(90deg, #6C5CE7, #a855f7);
36
+ -webkit-background-clip: text;
37
+ -webkit-text-fill-color: transparent;
38
+ background-clip: text;
39
+ }
40
+ .brand .tagline {
41
+ margin-top: 4px;
42
+ font-size: 11px;
43
+ color: #6b7280;
44
+ letter-spacing: 1.5px;
45
+ text-transform: uppercase;
46
+ }
47
+ .content { font-size: 13px; line-height: 1.6; color: #374151; }
48
+ .content h2 { color: #111827; font-size: 22px; margin: 0 0 24px; }
49
+ .footer {
50
+ margin-top: 48px;
51
+ text-align: center;
52
+ color: #9ca3af;
53
+ font-size: 11px;
54
+ border-top: 1px solid #ede9fe;
55
+ padding-top: 16px;
56
+ }
57
+ .muted { color: #6b7280; }
58
+ .row { display: flex; justify-content: space-between; align-items: flex-start; gap: 24px; }
59
+ .card {
60
+ background-color: #faf9ff;
61
+ border: 1px solid #ede9fe;
62
+ border-radius: 10px;
63
+ padding: 16px 20px;
64
+ }
65
+ table { width: 100%; border-collapse: collapse; margin-top: 16px; }
66
+ table th, table td { text-align: left; padding: 10px 12px; font-size: 13px; }
67
+ table thead th {
68
+ background: linear-gradient(90deg, #6C5CE7, #a855f7);
69
+ color: #ffffff;
70
+ font-weight: 600;
71
+ }
72
+ table tbody tr:nth-child(even) { background-color: #faf9ff; }
73
+ .total-row td {
74
+ border-top: 2px solid #6C5CE7;
75
+ font-weight: 700;
76
+ color: #111827;
77
+ }
78
+ .label { color: #6b7280; font-size: 11px; text-transform: uppercase; letter-spacing: 1px; }
79
+ .value { font-size: 14px; color: #111827; font-weight: 600; }
80
+ </style>
81
+ </head>
82
+ <body>
83
+ <div class="page">
84
+ {{> header}}
85
+ <div class="content">
86
+ {{{body}}}
87
+ </div>
88
+ {{> footer}}
89
+ </div>
90
+ </body>
91
+ </html>
@@ -0,0 +1,4 @@
1
+ <div class="footer">
2
+ <p>&copy; {{currentYear}} Duvdu. All rights reserved.</p>
3
+ <p>This document was generated automatically.</p>
4
+ </div>
@@ -0,0 +1,4 @@
1
+ <div class="brand">
2
+ <h1>Duvdu</h1>
3
+ <div class="tagline">Where creativity meets opportunity</div>
4
+ </div>
@@ -0,0 +1,60 @@
1
+ <h2>Invoice</h2>
2
+
3
+ <div class="row" style="margin-bottom: 28px;">
4
+ <div>
5
+ <div class="label">Transaction ID</div>
6
+ <div class="value">{{transactionId}}</div>
7
+ </div>
8
+ <div style="text-align: right;">
9
+ <div class="label">Date</div>
10
+ <div class="value">{{formattedDate}}</div>
11
+ </div>
12
+ </div>
13
+
14
+ <div class="row" style="margin-bottom: 28px;">
15
+ {{#if sp}}
16
+ <div class="card" style="flex: 1;">
17
+ <div class="label">Service Provider</div>
18
+ {{#if sp.name}}<div class="value" style="margin-top: 6px;">{{sp.name}}</div>{{/if}}
19
+ {{#if sp.email}}<div class="muted">{{sp.email}}</div>{{/if}}
20
+ {{#if sp.phone}}<div class="muted">{{sp.phone}}</div>{{/if}}
21
+ </div>
22
+ {{/if}}
23
+ {{#if client}}
24
+ <div class="card" style="flex: 1;">
25
+ <div class="label">Client</div>
26
+ {{#if client.name}}<div class="value" style="margin-top: 6px;">{{client.name}}</div>{{/if}}
27
+ {{#if client.email}}<div class="muted">{{client.email}}</div>{{/if}}
28
+ {{#if client.phone}}<div class="muted">{{client.phone}}</div>{{/if}}
29
+ </div>
30
+ {{/if}}
31
+ </div>
32
+
33
+ <table>
34
+ <thead>
35
+ <tr>
36
+ <th>Description</th>
37
+ <th style="text-align: right;">Amount ({{currency}})</th>
38
+ </tr>
39
+ </thead>
40
+ <tbody>
41
+ <tr>
42
+ <td>Base</td>
43
+ <td style="text-align: right;">{{formatMoney breakdown.base}}</td>
44
+ </tr>
45
+ <tr>
46
+ <td>VAT</td>
47
+ <td style="text-align: right;">{{formatMoney breakdown.vat}}</td>
48
+ </tr>
49
+ {{#each breakdown.fees}}
50
+ <tr>
51
+ <td>{{this.label}}</td>
52
+ <td style="text-align: right;">{{formatMoney this.value}}</td>
53
+ </tr>
54
+ {{/each}}
55
+ <tr class="total-row">
56
+ <td>Total</td>
57
+ <td style="text-align: right;">{{formatMoney total}}</td>
58
+ </tr>
59
+ </tbody>
60
+ </table>
@@ -0,0 +1,16 @@
1
+ import { PdfType, PdfContextMap } from '../types/pdf.types';
2
+ declare class PdfGeneratorServiceClass {
3
+ private browser;
4
+ private partialsRegistered;
5
+ generatePdf<T extends PdfType>(type: T, context: PdfContextMap[T]): Promise<Buffer>;
6
+ private prepareForType;
7
+ private prepareInvoice;
8
+ private registerHelpers;
9
+ private registerPartialsOnce;
10
+ private renderHtml;
11
+ private getBrowser;
12
+ private htmlToPdf;
13
+ close(): Promise<void>;
14
+ }
15
+ export declare const PdfGeneratorService: PdfGeneratorServiceClass;
16
+ export {};
@@ -0,0 +1,198 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
36
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
37
+ return new (P || (P = Promise))(function (resolve, reject) {
38
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
39
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
40
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
41
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
42
+ });
43
+ };
44
+ var __importDefault = (this && this.__importDefault) || function (mod) {
45
+ return (mod && mod.__esModule) ? mod : { "default": mod };
46
+ };
47
+ Object.defineProperty(exports, "__esModule", { value: true });
48
+ exports.PdfGeneratorService = void 0;
49
+ const fs = __importStar(require("fs"));
50
+ const path_1 = require("path");
51
+ const handlebars = __importStar(require("handlebars"));
52
+ const puppeteer_1 = __importDefault(require("puppeteer"));
53
+ const pdf_generation_error_1 = require("../errors/pdf-generation-error");
54
+ const pdf_types_1 = require("../types/pdf.types");
55
+ const mask_1 = require("../utils/mask");
56
+ class PdfGeneratorServiceClass {
57
+ constructor() {
58
+ this.browser = null;
59
+ this.partialsRegistered = false;
60
+ }
61
+ generatePdf(type, context) {
62
+ return __awaiter(this, void 0, void 0, function* () {
63
+ try {
64
+ const { templateName, documentTitle, prepared } = this.prepareForType(type, context);
65
+ const html = yield this.renderHtml(templateName, documentTitle, prepared);
66
+ return yield this.htmlToPdf(html);
67
+ }
68
+ catch (err) {
69
+ if (err instanceof pdf_generation_error_1.PdfGenerationError)
70
+ throw err;
71
+ const message = err instanceof Error ? err.message : 'unknown error';
72
+ throw new pdf_generation_error_1.PdfGenerationError(`Failed to generate PDF: ${message}`);
73
+ }
74
+ });
75
+ }
76
+ prepareForType(type, context) {
77
+ switch (type) {
78
+ case pdf_types_1.PdfType.INVOICE:
79
+ return this.prepareInvoice(context);
80
+ default:
81
+ throw new pdf_generation_error_1.PdfGenerationError(`Unsupported PDF type: ${type}`);
82
+ }
83
+ }
84
+ prepareInvoice(ctx) {
85
+ var _a;
86
+ const currency = (_a = ctx.currency) !== null && _a !== void 0 ? _a : 'EGP';
87
+ const dateObj = ctx.date instanceof Date ? ctx.date : new Date(ctx.date);
88
+ const formattedDate = isNaN(dateObj.getTime())
89
+ ? String(ctx.date)
90
+ : dateObj.toISOString().slice(0, 10);
91
+ const sp = ctx.recipient === 'sp' ? ctx.sp : (0, mask_1.maskParty)(ctx.sp);
92
+ const client = ctx.recipient === 'client' ? ctx.client : (0, mask_1.maskParty)(ctx.client);
93
+ return {
94
+ templateName: 'invoice',
95
+ documentTitle: `Invoice ${ctx.transactionId}`,
96
+ prepared: {
97
+ transactionId: ctx.transactionId,
98
+ formattedDate,
99
+ currency,
100
+ breakdown: ctx.breakdown,
101
+ total: ctx.total,
102
+ sp,
103
+ client,
104
+ },
105
+ };
106
+ }
107
+ registerHelpers() {
108
+ if (!handlebars.helpers['formatMoney']) {
109
+ handlebars.registerHelper('formatMoney', (value) => {
110
+ const num = typeof value === 'number' ? value : Number(value);
111
+ if (!Number.isFinite(num))
112
+ return '0.00';
113
+ return num.toFixed(2);
114
+ });
115
+ }
116
+ }
117
+ registerPartialsOnce(partialsDir) {
118
+ return __awaiter(this, void 0, void 0, function* () {
119
+ if (this.partialsRegistered)
120
+ return;
121
+ if (!fs.existsSync(partialsDir)) {
122
+ this.partialsRegistered = true;
123
+ return;
124
+ }
125
+ const files = yield fs.promises.readdir(partialsDir);
126
+ for (const file of files) {
127
+ if (!file.endsWith('.hbs'))
128
+ continue;
129
+ const partialName = file.replace('.hbs', '');
130
+ const content = yield fs.promises.readFile((0, path_1.join)(partialsDir, file), 'utf-8');
131
+ handlebars.registerPartial(partialName, content);
132
+ }
133
+ this.partialsRegistered = true;
134
+ });
135
+ }
136
+ renderHtml(templateName, documentTitle, context) {
137
+ return __awaiter(this, void 0, void 0, function* () {
138
+ const templatesDir = (0, path_1.join)(__dirname, 'pdf-templates');
139
+ const layoutsDir = (0, path_1.join)(templatesDir, 'layouts');
140
+ const partialsDir = (0, path_1.join)(templatesDir, 'partials');
141
+ const viewsDir = (0, path_1.join)(templatesDir, 'views');
142
+ this.registerHelpers();
143
+ yield this.registerPartialsOnce(partialsDir);
144
+ const viewPath = (0, path_1.join)(viewsDir, `${templateName}.hbs`);
145
+ if (!fs.existsSync(viewPath)) {
146
+ throw new pdf_generation_error_1.PdfGenerationError(`PDF template not found: ${templateName}`);
147
+ }
148
+ const viewContent = yield fs.promises.readFile(viewPath, 'utf-8');
149
+ const viewTemplate = handlebars.compile(viewContent);
150
+ const renderedView = viewTemplate(context);
151
+ const layoutPath = (0, path_1.join)(layoutsDir, 'main.hbs');
152
+ if (!fs.existsSync(layoutPath)) {
153
+ throw new pdf_generation_error_1.PdfGenerationError('PDF layout not found');
154
+ }
155
+ const layoutContent = yield fs.promises.readFile(layoutPath, 'utf-8');
156
+ const layoutTemplate = handlebars.compile(layoutContent);
157
+ return layoutTemplate(Object.assign(Object.assign({}, context), { documentTitle, currentYear: new Date().getFullYear(), body: renderedView }));
158
+ });
159
+ }
160
+ getBrowser() {
161
+ return __awaiter(this, void 0, void 0, function* () {
162
+ if (this.browser && this.browser.connected)
163
+ return this.browser;
164
+ this.browser = yield puppeteer_1.default.launch({
165
+ headless: true,
166
+ args: ['--no-sandbox', '--disable-setuid-sandbox'],
167
+ });
168
+ return this.browser;
169
+ });
170
+ }
171
+ htmlToPdf(html) {
172
+ return __awaiter(this, void 0, void 0, function* () {
173
+ const browser = yield this.getBrowser();
174
+ const page = yield browser.newPage();
175
+ try {
176
+ yield page.setContent(html, { waitUntil: 'networkidle0' });
177
+ const pdf = yield page.pdf({
178
+ format: 'A4',
179
+ printBackground: true,
180
+ margin: { top: '0', right: '0', bottom: '0', left: '0' },
181
+ });
182
+ return Buffer.from(pdf);
183
+ }
184
+ finally {
185
+ yield page.close();
186
+ }
187
+ });
188
+ }
189
+ close() {
190
+ return __awaiter(this, void 0, void 0, function* () {
191
+ if (this.browser) {
192
+ yield this.browser.close();
193
+ this.browser = null;
194
+ }
195
+ });
196
+ }
197
+ }
198
+ exports.PdfGeneratorService = new PdfGeneratorServiceClass();
@@ -1,9 +1,9 @@
1
1
  import { Document } from 'mongoose';
2
2
  import { Iuser } from '../types/User';
3
3
  export type UserDocument = Document & Iuser;
4
- export declare const updateRankForUser: (userId: string) => Promise<(Document<unknown, {}, Iuser, {}, {}> & Iuser & {
4
+ export declare const updateRankForUser: (userId: string) => Promise<(Document<unknown, {}, Iuser, {}, {}> & Iuser & Required<{
5
5
  _id: import("mongoose").Types.ObjectId;
6
- } & {
6
+ }> & {
7
7
  __v: number;
8
8
  }) | undefined>;
9
9
  export declare const updateUsersAfterRankDeletion: (deletedRankTitle: string) => Promise<void>;