Skip to content

Commit 3929a2d

Browse files
committed
feat: Enhance CV management features
1 parent 2021009 commit 3929a2d

File tree

16 files changed

+1349
-244
lines changed

16 files changed

+1349
-244
lines changed

backend/package-lock.json

Lines changed: 313 additions & 40 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

backend/package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,8 @@
1818
"express": "^5.1.0",
1919
"jsonwebtoken": "^9.0.2",
2020
"mongoose": "^8.18.0",
21-
"multer": "^2.0.2",
21+
"multer": "^1.4.4",
22+
"multer-gridfs-storage": "^5.0.2",
2223
"nodemon": "^3.1.10"
2324
}
2425
}

backend/src/controllers/cv.controller.js

Lines changed: 66 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,72 @@ const getCV = asyncHandler(async (req, res) => {
3838
);
3939
});
4040

41+
const uploadGovCV = asyncHandler(async (req, res) => {
42+
const userId = req.user._id;
43+
44+
if (!req.file) {
45+
throw new ApiError(400, "Government CV file is required");
46+
}
47+
48+
// File validation (GridFS middleware handles the actual storage)
49+
if (!req.file.gridfsId) {
50+
throw new ApiError(500, "File upload to GridFS failed");
51+
}
52+
53+
const govCVData = {
54+
userId: userId,
55+
originalName: req.file.originalname,
56+
filename: req.file.gridfsFilename,
57+
fileId: req.file.gridfsId, // GridFS file ID
58+
size: req.file.size,
59+
uploadDate: new Date(),
60+
type: 'government',
61+
status: 'pending'
62+
};
63+
64+
const existingGovCV = await CV.findOne({
65+
userId,
66+
'govCV.type': 'government'
67+
});
68+
69+
if (existingGovCV) {
70+
const updatedCV = await CV.findOneAndUpdate(
71+
{ userId },
72+
{
73+
$set: {
74+
'govCV': govCVData,
75+
lastModified: new Date()
76+
}
77+
},
78+
{ new: true }
79+
);
80+
return res.status(200).json(new ApiResponse(200, updatedCV, "Government CV uploaded successfully"));
81+
} else {
82+
const existingCV = await CV.findOne({ userId });
83+
84+
if (existingCV) {
85+
const updatedCV = await CV.findOneAndUpdate(
86+
{ userId },
87+
{
88+
$set: {
89+
govCV: govCVData,
90+
lastModified: new Date()
91+
}
92+
},
93+
{ new: true }
94+
);
95+
return res.status(200).json(new ApiResponse(200, updatedCV, "Government CV uploaded successfully"));
96+
} else {
97+
const newCV = await CV.create({
98+
userId: userId,
99+
govCV: govCVData,
100+
});
101+
return res.status(201).json(new ApiResponse(201, newCV, "Government CV uploaded and record created successfully"));
102+
}
103+
}
104+
});
41105
export {
42106
createOrUpdateCV,
43-
getCV
107+
getCV,
108+
uploadGovCV
44109
}
Lines changed: 62 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,72 @@
11
import multer from 'multer';
2-
import path from 'path';
3-
import { ApiError } from '../utils/ApiError';
4-
5-
const storage=multer.diskStorage({
6-
destination:function(req,file,cb){
7-
cb(null,path.join(path.resolve(),"uploads"));
8-
},
9-
filename:function(req,file,cb){
10-
const uniqueSuffix=Date.now()+"-"+Math.round(Math.random()*1E9);
11-
cb(null, req.user._id + '-' + uniqueSuffix + path.extname(file.originalname));
12-
}
2+
import { GridFSBucket } from 'mongodb';
3+
import mongoose from 'mongoose';
4+
import { ApiError } from '../utils/ApiError.js';
5+
6+
// Create GridFS bucket
7+
let bucket;
8+
mongoose.connection.once('open', () => {
9+
bucket = new GridFSBucket(mongoose.connection.db, {
10+
bucketName: 'uploads'
11+
});
1312
});
1413

15-
const fileFilter = (req, file, cb) => {
16-
// Allow only PDF, DOC, DOCX files
17-
const allowedTypes = /pdf|doc|docx/;
18-
const extname = allowedTypes.test(path.extname(file.originalname).toLowerCase());
19-
const mimetype = allowedTypes.test(file.mimetype);
14+
// GridFS storage for multer
15+
const gridFSStorage = multer.memoryStorage();
16+
17+
const pdfFileFilter = (req, file, cb) => {
18+
if (file.mimetype === 'application/pdf') {
19+
cb(null, true);
20+
} else {
21+
cb(new ApiError(400, "Only PDF files are allowed"));
22+
}
23+
};
2024

21-
if (mimetype && extname) {
22-
return cb(null, true);
25+
const documentFileFilter = (req, file, cb) => {
26+
const allowedTypes = ['application/pdf', 'application/msword', 'application/vnd.openxmlformats-officedocument.wordprocessingml.document'];
27+
if (allowedTypes.includes(file.mimetype)) {
28+
cb(null, true);
2329
} else {
2430
cb(new ApiError(400, "Only PDF, DOC, and DOCX files are allowed"));
2531
}
2632
};
2733

28-
const upload = multer({ storage, fileFilter });
34+
// Middleware to save to GridFS after multer processes the file
35+
const saveToGridFS = (req, res, next) => {
36+
if (!req.file || !bucket) {
37+
return next();
38+
}
39+
40+
const uniqueSuffix = Date.now() + "-" + Math.round(Math.random() * 1E9);
41+
const filename = req.user._id + '-' + uniqueSuffix + '-' + req.file.originalname;
42+
43+
const uploadStream = bucket.openUploadStream(filename, {
44+
metadata: { uploadedBy: req.user._id }
45+
});
46+
47+
uploadStream.end(req.file.buffer);
48+
49+
uploadStream.on('finish', () => {
50+
req.file.gridfsId = uploadStream.id;
51+
req.file.gridfsFilename = filename;
52+
next();
53+
});
54+
55+
uploadStream.on('error', (error) => {
56+
next(error);
57+
});
58+
};
59+
60+
const upload = multer({
61+
storage: gridFSStorage,
62+
fileFilter: documentFileFilter,
63+
limits: { fileSize: 10 * 1024 * 1024 }
64+
});
65+
66+
const uploadPDF = multer({
67+
storage: gridFSStorage,
68+
fileFilter: pdfFileFilter,
69+
limits: { fileSize: 5 * 1024 * 1024 }
70+
});
2971

30-
export { upload };
72+
export { upload, uploadPDF, saveToGridFS };

backend/src/models/cv.model.js

Lines changed: 76 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,23 @@ const cvSchema = new mongoose.Schema({
2020
type: String,
2121
required: true
2222
},
23+
gender: {
24+
type: String,
25+
enum: ['male', 'female', 'other', 'prefer-not-to-say'],
26+
default: ""
27+
},
28+
nationality: {
29+
type: String,
30+
default: ""
31+
},
32+
usmleId: {
33+
type: String,
34+
default: ""
35+
},
36+
address: {
37+
type: String,
38+
default: ""
39+
},
2340
medicalSchool: {
2441
type: String,
2542
required: true
@@ -28,10 +45,28 @@ const cvSchema = new mongoose.Schema({
2845
type: String,
2946
required: true
3047
},
48+
mbbsRegNo: {
49+
type: String,
50+
default: ""
51+
},
3152
city: {
3253
type: String,
3354
required: true
34-
}
55+
},
56+
photo: {
57+
type: String, // GridFS file ID for profile photo
58+
default: null
59+
},
60+
languages: [{
61+
language: {
62+
type: String,
63+
enum: ['English', 'Hindi', 'Spanish', 'French', 'German', 'Mandarin', 'Arabic', 'Portuguese', 'Russian', 'Japanese']
64+
},
65+
fluency: {
66+
type: String,
67+
enum: ['native', 'fluent', 'conversational', 'basic', 'beginner']
68+
}
69+
}]
3570
},
3671
education: {
3772
medicalSchoolName: {
@@ -191,6 +226,46 @@ const cvSchema = new mongoose.Schema({
191226
type: String,
192227
default: ""
193228
}
229+
},
230+
// Government CV upload section
231+
govCV: {
232+
userId: {
233+
type: mongoose.Schema.Types.ObjectId,
234+
ref: 'User'
235+
},
236+
originalName: {
237+
type: String
238+
},
239+
filename: {
240+
type: String
241+
},
242+
fileId: {
243+
type: String // GridFS file ID
244+
},
245+
size: {
246+
type: Number
247+
},
248+
uploadDate: {
249+
type: Date,
250+
default: Date.now
251+
},
252+
type: {
253+
type: String,
254+
default: 'government'
255+
},
256+
status: {
257+
type: String,
258+
enum: ['pending', 'approved', 'rejected'],
259+
default: 'pending'
260+
}
261+
},
262+
lastModified: {
263+
type: Date,
264+
default: Date.now
265+
},
266+
isComplete: {
267+
type: Boolean,
268+
default: false
194269
}
195270
}, {
196271
timestamps: true

backend/src/routes/cv.routes.js

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,16 @@
1-
import {Router} from 'express';
2-
import {createOrUpdateCV,getCV} from '../controllers/cv.controller.js';
1+
import { Router } from 'express';
2+
import { createOrUpdateCV, getCV, uploadGovCV } from '../controllers/cv.controller.js';
33
import { verifyJWT } from '../middlewares/auth.middleware.js';
4+
import { uploadPDF, saveToGridFS } from '../middlewares/multer.middleware.js';
45
const router = Router();
56

67
router.route('/save').post(verifyJWT, createOrUpdateCV);
78
router.route('/:userId').get(verifyJWT, getCV);
9+
router.route('/upload-gov-csv').post(
10+
verifyJWT,
11+
uploadPDF.single('govCV'),
12+
saveToGridFS,
13+
uploadGovCV
14+
);
815

916
export default router;
Binary file not shown.
Binary file not shown.

0 commit comments

Comments
 (0)