jp-shared 1.0.0
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/configs/firebase-admin.js +24 -0
- package/configs/index.js +3 -0
- package/constants/configuration.js +6 -0
- package/constants/database.js +6 -0
- package/constants/index.js +6 -0
- package/constants/org.js +30 -0
- package/constants/time-constant.js +4 -0
- package/helpers/index.js +4 -0
- package/helpers/otp-helper.js +48 -0
- package/helpers/validateMongoId.js +13 -0
- package/index.js +15 -0
- package/models/admin-models/admin-model.js +62 -0
- package/models/admin-models/index.js +4 -0
- package/models/admin-models/token-model.js +20 -0
- package/models/candidate-models/candidateModel.js +375 -0
- package/models/candidate-models/index.js +8 -0
- package/models/candidate-models/mobileOTPModel.js +75 -0
- package/models/candidate-models/otpModel.js +75 -0
- package/models/candidate-models/requestModel.js +26 -0
- package/models/candidate-models/savedJobModel.js +20 -0
- package/models/candidate-models/searchAppearanceModel.js +50 -0
- package/models/candidate-models/tokenModel.js +20 -0
- package/models/common-models/course-model.js +20 -0
- package/models/common-models/downloadedResumeHistory-model.js +54 -0
- package/models/common-models/education-model.js +17 -0
- package/models/common-models/index.js +19 -0
- package/models/common-models/jobApplication-model.js +57 -0
- package/models/common-models/jobApproach-model.js +14 -0
- package/models/common-models/jobOffer-model.js +20 -0
- package/models/common-models/jobType-model.js +22 -0
- package/models/common-models/noticePeriod.js +14 -0
- package/models/common-models/paymentHistory-model.js +128 -0
- package/models/common-models/plan-model.js +144 -0
- package/models/common-models/role-model.js +22 -0
- package/models/common-models/searchAppearanceHistory-model.js +36 -0
- package/models/common-models/skill-model.js +21 -0
- package/models/common-models/sms-student-model.js +44 -0
- package/models/common-models/speakingLanguage.js +14 -0
- package/models/common-models/specialization-model.js +20 -0
- package/models/common-models/viewedProfileHistory-model.js +44 -0
- package/models/index.js +8 -0
- package/models/jobPost-models/active-model.js +71 -0
- package/models/jobPost-models/draft-model.js +42 -0
- package/models/jobPost-models/expired-model.js +46 -0
- package/models/jobPost-models/index.js +10 -0
- package/models/jobPost-models/invitation-model.js +72 -0
- package/models/jobPost-models/onHold-model.js +58 -0
- package/models/jobPost-models/pending-model.js +45 -0
- package/models/jobPost-models/rejected-model.js +51 -0
- package/models/jobPost-models/verification-model.js +50 -0
- package/models/organization-models/active-model.js +111 -0
- package/models/organization-models/index.js +4 -0
- package/models/organization-models/verification-model.js +52 -0
- package/models/recruiter-models/active-model.js +45 -0
- package/models/recruiter-models/basicDetail-model.js +113 -0
- package/models/recruiter-models/counter-model.js +13 -0
- package/models/recruiter-models/emailOTP-model.js +68 -0
- package/models/recruiter-models/index.js +10 -0
- package/models/recruiter-models/mobileOTP-model.js +88 -0
- package/models/recruiter-models/pending-model.js +44 -0
- package/models/recruiter-models/request-model.js +75 -0
- package/models/recruiter-models/token-model.js +15 -0
- package/models/recruiter-models/verification-model.js +63 -0
- package/package.json +28 -0
- package/schemes/address-schema.js +43 -0
- package/schemes/index.js +7 -0
- package/schemes/pendingJobPostStep1-schema.js +192 -0
- package/schemes/pendingJobPostStep2-scheme.js +117 -0
- package/schemes/pendingJobPostStep3-scheme.js +21 -0
- package/schemes/pendingJobPostStep4-scheme.js +22 -0
- package/utils/env-utils.js +12 -0
- package/utils/firebase-utils.js +220 -0
- package/utils/index.js +12 -0
- package/utils/invoiceTemplate.js +123 -0
- package/utils/otp-utils.js +23 -0
- package/utils/password-utils.js +49 -0
- package/utils/percentageWeights.js +106 -0
- package/utils/populate-utils.js +57 -0
- package/utils/sendEmail-utils.js +40 -0
- package/utils/sendInvoice-utils.js +139 -0
- package/utils/token-utils.js +18 -0
- package/utils/validate-utils.js +118 -0
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
const { Readable } = require("stream");
|
|
2
|
+
const { bucket } = require("../configs/firebase-admin");
|
|
3
|
+
const { validateFile, validateFilePath } = require("./validate-utils");
|
|
4
|
+
|
|
5
|
+
// Function to upload a file to Firebase Storage with size limit check
|
|
6
|
+
const uploadFileToFirebase = (file, file_path, fieldName) => {
|
|
7
|
+
return new Promise((resolve, reject) => {
|
|
8
|
+
const uploadedFile = file;
|
|
9
|
+
|
|
10
|
+
// console.log(uploadedFile, "uploading file-------------");
|
|
11
|
+
// console.log(uploadedFile.fieldName, "uploading file-------------");
|
|
12
|
+
|
|
13
|
+
// Check if file exists and matches the specified field name
|
|
14
|
+
if (!uploadedFile || uploadedFile.fieldname !== fieldName) {
|
|
15
|
+
reject(new Error(`Invalid ${fieldName} file.`));
|
|
16
|
+
return;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
// Check if uploadedFile has a valid buffer property
|
|
20
|
+
if (!Buffer.isBuffer(uploadedFile.buffer)) {
|
|
21
|
+
reject(new Error(`Invalid buffer data for ${fieldName} file.`));
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// Check file size (1 MB limit)
|
|
26
|
+
const maxSizeInBytes = 1048576; // 1 MB in bytes
|
|
27
|
+
if (uploadedFile.size > maxSizeInBytes) {
|
|
28
|
+
reject(new Error(`File size exceeds the limit of 1 MB.`));
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const fileName = `${file_path}`;
|
|
33
|
+
const fileRef = bucket.file(fileName);
|
|
34
|
+
const fileStream = fileRef.createWriteStream({
|
|
35
|
+
metadata: {
|
|
36
|
+
contentType: uploadedFile.mimetype,
|
|
37
|
+
},
|
|
38
|
+
resumable: false,
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
const bufferStream = Readable.from([uploadedFile.buffer]);
|
|
42
|
+
bufferStream.pipe(fileStream);
|
|
43
|
+
|
|
44
|
+
fileStream.on("error", (err) => {
|
|
45
|
+
console.error(
|
|
46
|
+
`Error uploading ${fieldName} file to Firebase Storage:`,
|
|
47
|
+
err
|
|
48
|
+
);
|
|
49
|
+
reject(err); // Reject with the error for proper error propagation
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
fileStream.on("finish", () => {
|
|
53
|
+
console.log(`${fieldName} file upload finished.`);
|
|
54
|
+
resolve(fileRef); // Resolve with the file reference for further processing
|
|
55
|
+
});
|
|
56
|
+
});
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
// Function to get the download URL of a file from Firebase Storage
|
|
60
|
+
const getFirebaseUrl = (file) => {
|
|
61
|
+
return new Promise((resolve, reject) => {
|
|
62
|
+
file.getSignedUrl(
|
|
63
|
+
{
|
|
64
|
+
action: "read",
|
|
65
|
+
expires: "01-01-2100",
|
|
66
|
+
},
|
|
67
|
+
(err, url) => {
|
|
68
|
+
if (err) {
|
|
69
|
+
console.error(
|
|
70
|
+
"Error getting download URL from Firebase Storage:",
|
|
71
|
+
err
|
|
72
|
+
);
|
|
73
|
+
reject(err); // Reject with the error for proper error propagation
|
|
74
|
+
} else {
|
|
75
|
+
resolve(url); // Resolve with the download URL
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
);
|
|
79
|
+
});
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
// Generic function to store a file and get its Firebase URL with size limit check
|
|
83
|
+
const storeFileAndGetFirebaseUrl = async (file, file_path, fieldName) => {
|
|
84
|
+
try {
|
|
85
|
+
const uploadedFile = await uploadFileToFirebase(file, file_path, fieldName);
|
|
86
|
+
const downloadUrl = await getFirebaseUrl(uploadedFile);
|
|
87
|
+
return downloadUrl; // Return the download URL
|
|
88
|
+
} catch (error) {
|
|
89
|
+
throw error; // Throw the error for handling at the higher level
|
|
90
|
+
}
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
async function deleteFile(filePath) {
|
|
94
|
+
try {
|
|
95
|
+
await bucket.file(filePath).delete();
|
|
96
|
+
console.log(`File ${filePath} deleted successfully.`);
|
|
97
|
+
} catch (error) {
|
|
98
|
+
console.error("Error deleting file:", error);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
async function moveFile(originalFilePath, newFilePath) {
|
|
103
|
+
try {
|
|
104
|
+
// Copy the file to the new location
|
|
105
|
+
await bucket.file(originalFilePath).copy(bucket.file(newFilePath));
|
|
106
|
+
console.log(`File moved from ${originalFilePath} to ${newFilePath}.`);
|
|
107
|
+
|
|
108
|
+
// Delete the original file
|
|
109
|
+
await bucket.file(originalFilePath).delete();
|
|
110
|
+
console.log(`File ${originalFilePath} deleted successfully.`);
|
|
111
|
+
} catch (error) {
|
|
112
|
+
console.error("Error moving file:", error);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
async function downloadFile(fileName, destinationPath) {
|
|
117
|
+
try {
|
|
118
|
+
const file = bucket.file(fileName);
|
|
119
|
+
const [data] = await file.download();
|
|
120
|
+
|
|
121
|
+
// Write the downloaded data to a file
|
|
122
|
+
require("fs").writeFileSync(destinationPath, data);
|
|
123
|
+
console.log(`File ${fileName} downloaded successfully!`);
|
|
124
|
+
} catch (error) {
|
|
125
|
+
console.error("Error downloading file:", error);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// Example usage:
|
|
130
|
+
// deleteFile("images/my-image.jpg");
|
|
131
|
+
|
|
132
|
+
// Example usage:
|
|
133
|
+
// downloadFile("images/my-image.jpg", "./downloaded-image.jpg");
|
|
134
|
+
|
|
135
|
+
// Example usage:
|
|
136
|
+
// uploadFile("./my-image.jpg", "images/my-image.jpg");
|
|
137
|
+
|
|
138
|
+
function getFilePathFromFirebaseURL(url) {
|
|
139
|
+
try {
|
|
140
|
+
const parsedUrl = new URL(url);
|
|
141
|
+
|
|
142
|
+
// Extract the pathname, which includes the bucket name and file path
|
|
143
|
+
const fullPath = parsedUrl.pathname;
|
|
144
|
+
|
|
145
|
+
// Remove the bucket name (e.g., "a-b-c.appspot.com/") from the path
|
|
146
|
+
const bucketName = process.env.STORAGE_BUCKET_NAME;
|
|
147
|
+
const filePath = fullPath.replace(`/${bucketName}/`, "");
|
|
148
|
+
console.log(filePath, "this is a file................");
|
|
149
|
+
return filePath;
|
|
150
|
+
} catch (error) {
|
|
151
|
+
console.error("Error extracting file path:", error);
|
|
152
|
+
return null;
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
const uploadFile = async (file, filePath) => {
|
|
157
|
+
try {
|
|
158
|
+
validateFile(file); // Assuming these are implemented
|
|
159
|
+
validateFilePath(filePath);
|
|
160
|
+
|
|
161
|
+
const storageFile = bucket.file(filePath);
|
|
162
|
+
const stream = storageFile.createWriteStream({
|
|
163
|
+
metadata: {
|
|
164
|
+
contentType: file.mimetype,
|
|
165
|
+
},
|
|
166
|
+
resumable: false,
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
const bufferStream = Readable.from([file.buffer]);
|
|
170
|
+
bufferStream.pipe(stream);
|
|
171
|
+
|
|
172
|
+
return new Promise((resolve, reject) => {
|
|
173
|
+
stream.on("error", (err) => {
|
|
174
|
+
logger.error("Error uploading file to Firebase Storage:", { err });
|
|
175
|
+
reject(err);
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
stream.on("finish", async () => {
|
|
179
|
+
try {
|
|
180
|
+
const [url] = await storageFile.getSignedUrl({
|
|
181
|
+
action: "read",
|
|
182
|
+
expires: "01-01-2100",
|
|
183
|
+
});
|
|
184
|
+
resolve(url);
|
|
185
|
+
} catch (error) {
|
|
186
|
+
logger.error("Error generating signed URL:", { error });
|
|
187
|
+
reject(error);
|
|
188
|
+
}
|
|
189
|
+
});
|
|
190
|
+
});
|
|
191
|
+
} catch (error) {
|
|
192
|
+
logger.error("Error in uploadFile function:", { error: error.message });
|
|
193
|
+
throw error;
|
|
194
|
+
}
|
|
195
|
+
};
|
|
196
|
+
|
|
197
|
+
async function deleteFile(firebaseLink) {
|
|
198
|
+
if (!firebaseLink) {
|
|
199
|
+
throw new Error("File path is required.");
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
try {
|
|
203
|
+
const file = bucket.file(firebaseLink);
|
|
204
|
+
await file.delete();
|
|
205
|
+
console.log(`File at path ${firebaseLink} deleted successfully.`);
|
|
206
|
+
} catch (error) {
|
|
207
|
+
console.error(
|
|
208
|
+
`Error deleting file at path ${firebaseLink}: ${error.message}`
|
|
209
|
+
);
|
|
210
|
+
throw new Error("Failed to delete file from Firebase Storage.");
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
module.exports = {
|
|
215
|
+
storeFileAndGetFirebaseUrl,
|
|
216
|
+
getFirebaseUrl,
|
|
217
|
+
uploadFile,
|
|
218
|
+
deleteFile,
|
|
219
|
+
getFilePathFromFirebaseURL,
|
|
220
|
+
};
|
package/utils/index.js
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
module.exports = {
|
|
2
|
+
...require("./env-utils"),
|
|
3
|
+
...require("./percentageWeights"),
|
|
4
|
+
...require("./sendEmail-utils"),
|
|
5
|
+
...require("./validate-utils"),
|
|
6
|
+
...require("./firebase-utils"),
|
|
7
|
+
...require("./password-utils"),
|
|
8
|
+
...require("./token-utils"),
|
|
9
|
+
...require("./otp-utils"),
|
|
10
|
+
...require("./populate-utils"),
|
|
11
|
+
...require("./sendInvoice-utils"),
|
|
12
|
+
};
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
module.exports.invoiceEmailTemplate = ({
|
|
2
|
+
date = "06-12-2024",
|
|
3
|
+
billNo = "OAJPO1",
|
|
4
|
+
jobId = "JP24019",
|
|
5
|
+
items = [{ description: "Standard Plan", amount: 399 }],
|
|
6
|
+
totalAmount = "470.82",
|
|
7
|
+
totalAmountWords = "Four Hundred Seventy Rupees and Eighty-Two Paise Only",
|
|
8
|
+
gstPercentage = "18",
|
|
9
|
+
gstAmount = "6",
|
|
10
|
+
companyAddress,
|
|
11
|
+
companyName,
|
|
12
|
+
gstNumber,
|
|
13
|
+
logoUrl,
|
|
14
|
+
supportEmail,
|
|
15
|
+
supportMobile,
|
|
16
|
+
termsUrl,
|
|
17
|
+
}) => {
|
|
18
|
+
return `
|
|
19
|
+
<!DOCTYPE html>
|
|
20
|
+
<html lang="en">
|
|
21
|
+
<head>
|
|
22
|
+
<meta charset="UTF-8">
|
|
23
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
24
|
+
<title>Invoice</title>
|
|
25
|
+
|
|
26
|
+
</head>
|
|
27
|
+
<body>
|
|
28
|
+
<div style="max-width: 800px; margin: 0 auto; font-family: Arial, sans-serif; font-size: 14px; border: 1px solid #000; padding: 20px; background-color: #fff;">
|
|
29
|
+
<!-- Header Section -->
|
|
30
|
+
<div style="overflow: hidden; margin-bottom: 20px; width: 100%;">
|
|
31
|
+
<!-- Logo Section -->
|
|
32
|
+
<div style="float: left;">
|
|
33
|
+
<img src="${logoUrl}" alt="Company Logo" style="width: 150px; height: auto;" />
|
|
34
|
+
</div>
|
|
35
|
+
<!-- Invoice and Date Section -->
|
|
36
|
+
<div style="float: right; text-align: right;">
|
|
37
|
+
<h2 style="margin: 0;">Invoice</h2>
|
|
38
|
+
<p style="margin: 5px 0;"><strong>Date:</strong> ${date}</p>
|
|
39
|
+
</div>
|
|
40
|
+
</div>
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
<!-- Bill and Company Details -->
|
|
45
|
+
<div style="width: 100%; margin-bottom: 20px; font-size: 14px;">
|
|
46
|
+
<!-- Bill Details -->
|
|
47
|
+
<div style="display: inline-block; width: 48%; vertical-align: top;">
|
|
48
|
+
<p style="margin: 5px 0;"><strong>Bill No:</strong> ${billNo}</p>
|
|
49
|
+
<p style="margin: 5px 0;"><strong>Job ID:</strong> ${jobId}</p>
|
|
50
|
+
</div>
|
|
51
|
+
<!-- Company Name and Address -->
|
|
52
|
+
<div style="display: inline-block; width: 48%; text-align: right; vertical-align: top;">
|
|
53
|
+
<p style="margin: 5px 0;"><strong>${companyName}</strong></p>
|
|
54
|
+
<p style="margin: 5px 0;">${companyAddress}</p>
|
|
55
|
+
</div>
|
|
56
|
+
</div>
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
<!-- Table Section -->
|
|
60
|
+
<table style="width: 100%; border-collapse: collapse; margin-top: 20px; font-size: 14px;">
|
|
61
|
+
<thead>
|
|
62
|
+
<tr>
|
|
63
|
+
<th style="border: 1px solid #000; padding: 10px; background-color: #f4f4f4;">S. No</th>
|
|
64
|
+
<th style="border: 1px solid #000; padding: 10px; background-color: #f4f4f4;">Description of Service</th>
|
|
65
|
+
<th style="border: 1px solid #000; padding: 10px; background-color: #f4f4f4;">Amount</th>
|
|
66
|
+
</tr>
|
|
67
|
+
</thead>
|
|
68
|
+
<tbody>
|
|
69
|
+
${items
|
|
70
|
+
.map(
|
|
71
|
+
(item, index) => `
|
|
72
|
+
<tr>
|
|
73
|
+
<td style="border: 1px solid #000; padding: 10px; text-align: center;">${
|
|
74
|
+
index + 1
|
|
75
|
+
}</td>
|
|
76
|
+
<td style="border: 1px solid #000; padding: 10px; text-align: center;">${
|
|
77
|
+
item.description
|
|
78
|
+
}</td>
|
|
79
|
+
<td style="border: 1px solid #000; padding: 10px; text-align: center;">${
|
|
80
|
+
item.amount
|
|
81
|
+
}</td>
|
|
82
|
+
</tr>`
|
|
83
|
+
)
|
|
84
|
+
.join("")}
|
|
85
|
+
<tr>
|
|
86
|
+
<td colspan="2" style="text-align: right; border: 1px solid #000; padding: 10px;">GST (${gstPercentage}%)</td>
|
|
87
|
+
<td style="border: 1px solid #000; padding: 10px; text-align: center;">${gstAmount}</td>
|
|
88
|
+
</tr>
|
|
89
|
+
<tr>
|
|
90
|
+
<td colspan="2" style="text-align: right; border: 1px solid #000; padding: 10px;"><strong>Total Amount</strong></td>
|
|
91
|
+
<td style="border: 1px solid #000; padding: 10px; text-align: center;"><strong>${totalAmount}</strong></td>
|
|
92
|
+
</tr>
|
|
93
|
+
</tbody>
|
|
94
|
+
</table>
|
|
95
|
+
|
|
96
|
+
<!-- Total Amount in Words -->
|
|
97
|
+
<div style="margin-top: 20px; text-align: right;">
|
|
98
|
+
<p style="margin: 5px 0;"><strong>Total in Words:</strong> ${totalAmountWords}</p>
|
|
99
|
+
</div>
|
|
100
|
+
|
|
101
|
+
<!-- Footer Section -->
|
|
102
|
+
<div style="margin-top: 30px; border-top: 1px solid #000; padding-top: 10px; font-size: 14px;">
|
|
103
|
+
<!-- Support and GST Details -->
|
|
104
|
+
<div style="width: 100%; overflow: hidden;">
|
|
105
|
+
<!-- Left Section -->
|
|
106
|
+
<div style="display: inline-block; width: 48%; vertical-align: top;">
|
|
107
|
+
<p style="margin: 5px 0;"><strong>Email:</strong> ${supportEmail}</p>
|
|
108
|
+
<p style="margin: 5px 0;"><strong>Mobile Number:</strong> ${supportMobile}</p>
|
|
109
|
+
<a href="${termsUrl}" target="_blank" style="text-decoration: none; color: blue;">Terms and conditions</a>
|
|
110
|
+
</div>
|
|
111
|
+
<!-- Right Section -->
|
|
112
|
+
<div style="display: inline-block; width: 48%; text-align: right; vertical-align: top;">
|
|
113
|
+
<p style="margin: 5px 0;"><strong>GST No:</strong> ${gstNumber}</p>
|
|
114
|
+
</div>
|
|
115
|
+
</div>
|
|
116
|
+
</div>
|
|
117
|
+
|
|
118
|
+
</div>
|
|
119
|
+
|
|
120
|
+
</body>
|
|
121
|
+
</html>
|
|
122
|
+
`;
|
|
123
|
+
};
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
function generateOTP(otpLength) {
|
|
2
|
+
// Validate otpLength
|
|
3
|
+
if (typeof otpLength !== "number" || otpLength < 1 || otpLength % 1 !== 0) {
|
|
4
|
+
throw new Error("Invalid OTP length. It must be a positive integer.");
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
// Calculate the range for OTP generation
|
|
8
|
+
const min = Math.pow(10, otpLength - 1);
|
|
9
|
+
const max = Math.pow(10, otpLength) - 1;
|
|
10
|
+
|
|
11
|
+
// Handle edge case when otpLength is 1
|
|
12
|
+
if (otpLength === 1) {
|
|
13
|
+
return Math.floor(Math.random() * 10).toString(); // Generates a single digit OTP (0-9)
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
// Generate the OTP number within the specified range
|
|
17
|
+
const otpNumber = Math.floor(Math.random() * (max - min + 1)) + min;
|
|
18
|
+
|
|
19
|
+
// Convert the number to a string and return
|
|
20
|
+
return otpNumber.toString();
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
module.exports = { generateOTP };
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
const bcrypt = require("bcrypt");
|
|
2
|
+
|
|
3
|
+
// Function to hash a password
|
|
4
|
+
async function hashPassword(plaintextPassword) {
|
|
5
|
+
try {
|
|
6
|
+
// Generate a salt
|
|
7
|
+
const saltRounds = 10;
|
|
8
|
+
const salt = await bcrypt.genSalt(saltRounds);
|
|
9
|
+
|
|
10
|
+
// Hash the password with the salt
|
|
11
|
+
const hashedPassword = await bcrypt.hash(plaintextPassword, salt);
|
|
12
|
+
|
|
13
|
+
return hashedPassword;
|
|
14
|
+
} catch (error) {
|
|
15
|
+
// Handle errors appropriately
|
|
16
|
+
console.error("Error hashing password:", error);
|
|
17
|
+
throw new Error("Password hashing failed");
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
// Function to compare a plaintext password with a hashed password
|
|
22
|
+
async function comparePassword(plaintextPassword, hashedPassword) {
|
|
23
|
+
// Input validation
|
|
24
|
+
if (
|
|
25
|
+
typeof plaintextPassword !== "string" ||
|
|
26
|
+
typeof hashedPassword !== "string"
|
|
27
|
+
) {
|
|
28
|
+
throw new Error("Invalid input type");
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
if (!plaintextPassword || !hashedPassword) {
|
|
32
|
+
throw new Error("Passwords cannot be empty");
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
try {
|
|
36
|
+
// Compare the plaintext password with the hashed password
|
|
37
|
+
const isMatch = await bcrypt.compare(plaintextPassword, hashedPassword);
|
|
38
|
+
return isMatch;
|
|
39
|
+
} catch (error) {
|
|
40
|
+
console.error("Error comparing password:", error.message);
|
|
41
|
+
throw new Error("Password comparison failed");
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// Export the function
|
|
46
|
+
module.exports = {
|
|
47
|
+
hashPassword,
|
|
48
|
+
comparePassword,
|
|
49
|
+
};
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
// Define percentage weights for all fields
|
|
2
|
+
const percentageWeights = {
|
|
3
|
+
resume: 15,
|
|
4
|
+
skillIds: 10,
|
|
5
|
+
roleIds: 10,
|
|
6
|
+
linkedIn: 3,
|
|
7
|
+
workStatus: 4,
|
|
8
|
+
noticePeriod: 4,
|
|
9
|
+
profileSummary: 5,
|
|
10
|
+
avatar: 5,
|
|
11
|
+
fullName: 5,
|
|
12
|
+
email: 5,
|
|
13
|
+
mobileNumber: 6,
|
|
14
|
+
countryCode: 2,
|
|
15
|
+
maritalStatus: 2,
|
|
16
|
+
gender: 2,
|
|
17
|
+
dob: 2,
|
|
18
|
+
address: 5,
|
|
19
|
+
speakingLanguageIds: 3,
|
|
20
|
+
educations: 5, // 5% for educations
|
|
21
|
+
schools: 5, // 5% for schools
|
|
22
|
+
projects: 7,
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Calculates the score for a candidate based on provided fields.
|
|
27
|
+
* @param {Object} candidate - The candidate object containing various fields.
|
|
28
|
+
* @returns {number} The total score calculated based on the percentage weights.
|
|
29
|
+
*/
|
|
30
|
+
const calculateScore = (candidate) => {
|
|
31
|
+
if (!candidate || typeof candidate !== "object") {
|
|
32
|
+
throw new Error("Invalid candidate object");
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
let totalScore = 0;
|
|
36
|
+
|
|
37
|
+
// Destructure properties for cleaner access
|
|
38
|
+
const {
|
|
39
|
+
resume,
|
|
40
|
+
skillIds,
|
|
41
|
+
roleIds,
|
|
42
|
+
linkedIn,
|
|
43
|
+
workStatus,
|
|
44
|
+
noticePeriod,
|
|
45
|
+
profileSummary,
|
|
46
|
+
avatar,
|
|
47
|
+
fullName,
|
|
48
|
+
email,
|
|
49
|
+
mobileNumber,
|
|
50
|
+
countryCode,
|
|
51
|
+
maritalStatus,
|
|
52
|
+
gender,
|
|
53
|
+
dob,
|
|
54
|
+
address,
|
|
55
|
+
speakingLanguageIds,
|
|
56
|
+
educations,
|
|
57
|
+
schools,
|
|
58
|
+
projects,
|
|
59
|
+
} = candidate;
|
|
60
|
+
|
|
61
|
+
// Add score for each field based on existence
|
|
62
|
+
if (resume) totalScore += percentageWeights.resume;
|
|
63
|
+
if (Array.isArray(skillIds) && skillIds.length)
|
|
64
|
+
totalScore += percentageWeights.skillIds;
|
|
65
|
+
if (Array.isArray(roleIds) && roleIds.length)
|
|
66
|
+
totalScore += percentageWeights.roleIds;
|
|
67
|
+
if (linkedIn) totalScore += percentageWeights.linkedIn;
|
|
68
|
+
if (workStatus) totalScore += percentageWeights.workStatus;
|
|
69
|
+
if (noticePeriod) totalScore += percentageWeights.noticePeriod;
|
|
70
|
+
if (profileSummary) totalScore += percentageWeights.profileSummary;
|
|
71
|
+
if (avatar) totalScore += percentageWeights.avatar;
|
|
72
|
+
if (fullName) totalScore += percentageWeights.fullName;
|
|
73
|
+
if (email) totalScore += percentageWeights.email;
|
|
74
|
+
if (mobileNumber) totalScore += percentageWeights.mobileNumber;
|
|
75
|
+
if (countryCode) totalScore += percentageWeights.countryCode;
|
|
76
|
+
if (maritalStatus) totalScore += percentageWeights.maritalStatus;
|
|
77
|
+
if (gender) totalScore += percentageWeights.gender;
|
|
78
|
+
if (dob) totalScore += percentageWeights.dob;
|
|
79
|
+
if (address) totalScore += percentageWeights.address;
|
|
80
|
+
if (Array.isArray(speakingLanguageIds) && speakingLanguageIds.length)
|
|
81
|
+
totalScore += percentageWeights.speakingLanguageIds;
|
|
82
|
+
|
|
83
|
+
// Check for either educations or schools
|
|
84
|
+
if (
|
|
85
|
+
(Array.isArray(educations) && educations.length) ||
|
|
86
|
+
(Array.isArray(schools) && schools.length)
|
|
87
|
+
) {
|
|
88
|
+
totalScore += percentageWeights.educations; // Add 5% for educations if exists
|
|
89
|
+
}
|
|
90
|
+
// if (
|
|
91
|
+
// Array.isArray(schools) &&
|
|
92
|
+
// schools.length &&
|
|
93
|
+
// !(Array.isArray(educations) && educations.length)
|
|
94
|
+
// ) {
|
|
95
|
+
// totalScore += percentageWeights.schools; // Add 5% for schools only if educations don't exist
|
|
96
|
+
// }
|
|
97
|
+
|
|
98
|
+
// Add score for projects if they exist
|
|
99
|
+
if (Array.isArray(projects) && projects.length) {
|
|
100
|
+
totalScore += percentageWeights.projects;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
return totalScore;
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
module.exports = { percentageWeights, calculateScore };
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
const { SkillModel } = require("../models/common-models/skill-model");
|
|
2
|
+
const { RoleModel } = require("../models/common-models/role-model");
|
|
3
|
+
const {
|
|
4
|
+
ActiveOrganizationModel,
|
|
5
|
+
} = require("../models/organization-models/active-model");
|
|
6
|
+
const { EducationModel } = require("../models/common-models/education-model");
|
|
7
|
+
const {
|
|
8
|
+
SpecializationModel,
|
|
9
|
+
} = require("../models/common-models/specialization-model");
|
|
10
|
+
const { CourseModel } = require("../models/common-models/course-model");
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Applies common population and selection to job post queries.
|
|
14
|
+
* @param {Query} query - The Mongoose query to modify.
|
|
15
|
+
* @returns {Query} - The modified Mongoose query.
|
|
16
|
+
*/
|
|
17
|
+
const populateJobPostDetails = (query) => {
|
|
18
|
+
return query
|
|
19
|
+
.select("-createdAt -updatedAt -__v -planId") // Exclude specified fields
|
|
20
|
+
.populate({
|
|
21
|
+
path: "jobSkillIds",
|
|
22
|
+
model: SkillModel,
|
|
23
|
+
select: "skillName",
|
|
24
|
+
})
|
|
25
|
+
.populate({
|
|
26
|
+
path: "jobPreferredSkillIds",
|
|
27
|
+
model: SkillModel,
|
|
28
|
+
select: "skillName",
|
|
29
|
+
})
|
|
30
|
+
.populate({
|
|
31
|
+
path: "jobRoleIds",
|
|
32
|
+
model: RoleModel,
|
|
33
|
+
select: "roleName",
|
|
34
|
+
})
|
|
35
|
+
.populate({
|
|
36
|
+
path: "organizationId",
|
|
37
|
+
model: ActiveOrganizationModel,
|
|
38
|
+
select: "organizationName",
|
|
39
|
+
})
|
|
40
|
+
.populate({
|
|
41
|
+
path: "qualificationIds",
|
|
42
|
+
model: EducationModel,
|
|
43
|
+
select: "label",
|
|
44
|
+
})
|
|
45
|
+
.populate({
|
|
46
|
+
path: "courseIds",
|
|
47
|
+
model: CourseModel,
|
|
48
|
+
select: "label",
|
|
49
|
+
})
|
|
50
|
+
.populate({
|
|
51
|
+
path: "specializationIds",
|
|
52
|
+
model: SpecializationModel,
|
|
53
|
+
select: "label",
|
|
54
|
+
});
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
module.exports = { populateJobPostDetails };
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
const nodemailer = require("nodemailer");
|
|
2
|
+
|
|
3
|
+
const sendEmail = async (senderEmail, emailBody) => {
|
|
4
|
+
// Check if the required environment variables are set
|
|
5
|
+
if (!process.env.EMAIL || !process.env.EMAIL_PASSWORD) {
|
|
6
|
+
const errorMsg = "Email credentials are not set in environment variables.";
|
|
7
|
+
console.error(errorMsg);
|
|
8
|
+
throw new Error(errorMsg); // Throw error to propagate it back to the caller
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
// Create a transporter using your email credentials
|
|
12
|
+
const transporter = nodemailer.createTransport({
|
|
13
|
+
service: "gmail",
|
|
14
|
+
auth: {
|
|
15
|
+
user: process.env.EMAIL,
|
|
16
|
+
pass: process.env.EMAIL_PASSWORD, // SMTP Password
|
|
17
|
+
},
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
// Email options
|
|
21
|
+
const mailOptions = {
|
|
22
|
+
from: process.env.EMAIL,
|
|
23
|
+
to: senderEmail,
|
|
24
|
+
subject: "Welcome to our OA Job Portal!",
|
|
25
|
+
html: emailBody,
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
// Send the email
|
|
29
|
+
try {
|
|
30
|
+
const info = await transporter.sendMail(mailOptions);
|
|
31
|
+
console.log(`Email sent successfully to ${senderEmail}: ${info.response}`);
|
|
32
|
+
// return { success: true, message: `Email sent to ${senderEmail}` }; // Return a success message
|
|
33
|
+
} catch (error) {
|
|
34
|
+
const errorMsg = `Error sending email to ${senderEmail}: ${error.message}`;
|
|
35
|
+
console.error(errorMsg);
|
|
36
|
+
throw new Error(errorMsg); // Throw error to propagate it back to the caller
|
|
37
|
+
}
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
module.exports = { sendEmail };
|