init commit

This commit is contained in:
2025-07-07 00:52:41 +05:30
commit 2dd24927bc
81 changed files with 10274 additions and 0 deletions

3
backend/.env Normal file
View File

@ -0,0 +1,3 @@
MONGO_URI=mongodb://localhost:27017/mern_resume_builder
JWT_SECRET=9cd1f9e482ac014933a12ca44f3b1decfb2ee134ecd66203b4ad46a991cf216e69fd0058fb43741705724c32b31965d9416fc871ceafc48a624ab41dbd780905
PORT=8000

24
backend/.gitignore vendored Normal file
View File

@ -0,0 +1,24 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*
node_modules
dist
dist-ssr
*.local
# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
.DS_Store
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?

13
backend/config/db.js Normal file
View File

@ -0,0 +1,13 @@
const mongoose = require("mongoose");
const connectDB = async () => {
try {
await mongoose.connect(process.env.MONGO_URI, {});
console.log("MongoDB connected");
} catch (err) {
console.error("Error connecting to MongoDB", err);
process.exit(1);
}
};
module.exports = connectDB;

View File

@ -0,0 +1,94 @@
const User = require("../models/User");
const bcrypt = require("bcryptjs");
const jwt = require("jsonwebtoken");
// Generate JWT Token
const generateToken = (userId) => {
return jwt.sign({ id: userId }, process.env.JWT_SECRET, { expiresIn: "7d" });
};
// @desc Register a new user
// @route POST /api/auth/register
// @access Public
const registerUser = async (req, res) => {
try {
const { name, email, password, profileImageUrl } = req.body;
// Check if user already exists
const userExists = await User.findOne({ email });
if (userExists) {
return res.status(400).json({ message: "User already exists" });
}
// Hash password
const salt = await bcrypt.genSalt(10);
const hashedPassword = await bcrypt.hash(password, salt);
// Create new user
const user = await User.create({
name,
email,
password: hashedPassword,
profileImageUrl,
});
// Return user data with JWT
res.status(201).json({
_id: user._id,
name: user.name,
email: user.email,
profileImageUrl: user.profileImageUrl,
token: generateToken(user._id),
});
} catch (error) {
res.status(500).json({ message: "Server error", error: error.message });
}
};
// @desc Login user
// @route POST /api/auth/login
// @access Public
const loginUser = async (req, res) => {
try {
const { email, password } = req.body;
const user = await User.findOne({ email });
if (!user) {
return res.status(500).json({ message: "Invalid email or password" });
}
// Compare password
const isMatch = await bcrypt.compare(password, user.password);
if (!isMatch) {
return res.status(500).json({ message: "Invalid email or password" });
}
// Return user data with JWT
res.json({
_id: user._id,
name: user.name,
email: user.email,
profileImageUrl: user.profileImageUrl,
token: generateToken(user._id),
});
} catch (error) {
res.status(500).json({ message: "Server error", error: error.message });
}
};
// @desc Get user profile
// @route GET /api/auth/profile
// @access Private (Requires JWT)
const getUserProfile = async (req, res) => {
try {
const user = await User.findById(req.user.id).select("-password");
if (!user) {
return res.status(404).json({ message: "User not found" });
}
res.json(user);
} catch (error) {
res.status(500).json({ message: "Server error", error: error.message });
}
};
module.exports = { registerUser, loginUser, getUserProfile };

View File

@ -0,0 +1,204 @@
const fs = require("node:fs");
const path = require("node:path");
const Resume = require("../models/Resume");
// @desc Create a new resume
// @route POST /api/resumes
// @access Private
const createResume = async (req, res) => {
try {
const { title } = req.body;
// Default template
const defaultResumeData = {
profileInfo: {
profileImg: null,
previewUrl: "",
fullName: "",
designation: "",
summary: "",
},
contactInfo: {
email: "",
phone: "",
location: "",
linkedin: "",
github: "",
website: "",
},
workExperience: [
{
company: "",
role: "",
startDate: "",
endDate: "",
description: "",
},
],
education: [
{
degree: "",
institution: "",
startDate: "",
endDate: "",
},
],
skills: [
{
name: "",
progress: 0,
},
],
projects: [
{
title: "",
description: "",
github: "",
liveDemo: "",
},
],
certifications: [
{
title: "",
issuer: "",
year: "",
},
],
languages: [
{
name: "",
progress: 0,
},
],
interests: [""],
};
const newResume = await Resume.create({
userId: req.user._id,
title,
...defaultResumeData,
});
res.status(201).json(newResume);
} catch (error) {
res
.status(500)
.json({ message: "Failed to create resume", error: error.message });
}
};
// @desc Get all resumes for logged-in user
// @route GET /api/resumes
// @access Private
const getUserResumes = async (req, res) => {
try {
const resumes = await Resume.find({ userId: req.user._id }).sort({
updatedAt: -1,
});
res.json(resumes);
} catch (error) {
res
.status(500)
.json({ message: "Failed to create resume", error: error.message });
}
};
// @desc Get single resume by ID
// @route GET /api/resumes/:id
// @access Private
const getResumeById = async (req, res) => {
try {
const resume = await Resume.findOne({ _id: req.params.id, userId: req.user._id });
if (!resume) {
return res.status(404).json({ message: "Resume not found" });
}
res.json(resume);
} catch (error) {
res
.status(500)
.json({ message: "Failed to create resume", error: error.message });
}
};
// @desc Update a resume
// @route PUT /api/resumes/:id
// @access Private
const updateResume = async (req, res) => {
try {
const resume = await Resume.findOne({
_id: req.params.id,
userId: req.user._id,
});
if (!resume) {
return res.status(404).json({ message: "Resume not found or unauthorized" });
}
// Merge updates from req.body into existing resume
Object.assign(resume, req.body);
// Save updated resume
const savedResume = await resume.save();
res.json(savedResume);
} catch (error) {
res
.status(500)
.json({ message: "Failed to create resume", error: error.message });
}
};
// @desc Delete a resume
// @route DELETE /api/resumes/:id
// @access Private
const deleteResume = async (req, res) => {
try {
const resume = await Resume.findOne({
_id: req.params.id,
userId: req.user._id,
});
if (!resume) {
return res.status(404).json({ message: "Resume not found or unauthorized" });
}
// Delete thumbnailLink and profilePreviewUrl images from uploads folder
const uploadsFolder = path.join(__dirname, '..', 'uploads');
const baseUrl = `${req.protocol}://${req.get("host")}`;
if(resume.thumbnailLink){
const oldThumbnail = path.join(uploadsFolder, path.basename(resume.thumbnailLink));
if (fs.existsSync(oldThumbnail)) fs.unlinkSync(oldThumbnail);
}
if(resume.profileInfo?.profilePreviewUrl){
const oldProfile = path.join(uploadsFolder, path.basename(resume.profileInfo.profilePreviewUrl));
if (fs.existsSync(oldProfile)) fs.unlinkSync(oldProfile);
}
const deleted = await Resume.findOneAndDelete({
_id: req.params.id,
userId: req.user._id,
});
if (!deleted) {
return res.status(404).json({ message: "Resume not found or unauthorized" });
}
res.json({ message: "Resume deleted successfully" });
} catch (error) {
res
.status(500)
.json({ message: "Failed to create resume", error: error.message });
}
};
module.exports = {
createResume,
getUserResumes,
getResumeById,
updateResume,
deleteResume,
};

View File

@ -0,0 +1,58 @@
const fs = require("fs");
const path = require("path");
const Resume = require("../models/Resume");
const upload = require("../middlewares/uploadMiddleware");
const uploadResumeImages = async (req, res) => {
try {
upload.fields([{ name: 'thumbnail' }, { name: 'profileImage' }])(req, res, async (err) => {
if (err) {
return res.status(400).json({ message: "File upload failed", error: err.message });
}
const resumeId = req.params.id;
const resume = await Resume.findOne({ _id: resumeId, userId: req.user._id });
if (!resume) {
return res.status(404).json({ message: "Resume not found or unauthorized" });
}
const uploadsFolder = path.join(__dirname, '..', 'uploads');
const baseUrl = `${req.protocol}://${req.get("host")}`;
const newThumbnail = req.files.thumbnail?.[0];
const newProfileImage = req.files.profileImage?.[0];
// If new thumbnail uploaded, delete old one
if (newThumbnail) {
if(resume.thumbnailLink){
const oldThumbnail = path.join(uploadsFolder, path.basename(resume.thumbnailLink));
if (fs.existsSync(oldThumbnail)) fs.unlinkSync(oldThumbnail);
}
resume.thumbnailLink = `${baseUrl}/uploads/${newThumbnail.filename}`;
}
// If new profile image uploaded, delete old one
if (newProfileImage) {
if(resume.profileInfo?.profilePreviewUrl){
const oldProfile = path.join(uploadsFolder, path.basename(resume.profileInfo.profilePreviewUrl));
if (fs.existsSync(oldProfile)) fs.unlinkSync(oldProfile);
}
resume.profileInfo.profilePreviewUrl = `${baseUrl}/uploads/${newProfileImage.filename}`;
}
await resume.save();
res.status(200).json({
message: "Images uploaded successfully",
thumbnailLink: resume.thumbnailLink,
profilePreviewUrl: resume.profileInfo.profilePreviewUrl,
});
});
} catch (err) {
console.error("Error uploading images:", err);
res.status(500).json({ message: "Failed to upload images", error: err.message });
}
};
module.exports = { uploadResumeImages };

View File

@ -0,0 +1,22 @@
const jwt = require("jsonwebtoken");
const User = require("../models/User");
// Middleware to protect routes
const protect = async (req, res, next) => {
try {
let token = req.headers.authorization;
if (token && token.startsWith("Bearer")) {
token = token.split(" ")[1]; // Extract token
const decoded = jwt.verify(token, process.env.JWT_SECRET);
req.user = await User.findById(decoded.id).select("-password");
next();
} else {
res.status(401).json({ message: "Not authorized, no token" });
}
} catch (error) {
res.status(401).json({ message: "Token failed", error: error.message });
}
};
module.exports = { protect };

View File

@ -0,0 +1,25 @@
const multer = require('multer');
// Configure storage
const storage = multer.diskStorage({
destination: (req, file, cb) => {
cb(null, 'uploads/');
},
filename: (req, file, cb) => {
cb(null, `${Date.now()}-${file.originalname}`);
},
});
// File filter
const fileFilter = (req, file, cb) => {
const allowedTypes = ['image/jpeg', 'image/png', 'image/jpg'];
if (allowedTypes.includes(file.mimetype)) {
cb(null, true);
} else {
cb(new Error('Only .jpeg, .jpg and .png formats are allowed'), false);
}
};
const upload = multer({ storage, fileFilter });
module.exports = upload;

86
backend/models/Resume.js Normal file
View File

@ -0,0 +1,86 @@
const mongoose = require("mongoose");
const ResumeSchema = new mongoose.Schema(
{
userId: {
type: mongoose.Schema.Types.ObjectId,
ref: "User",
required: true,
},
title: {
type: String,
required: true,
},
thumbnailLink: {
type: String,
},
template:{
theme: String,
colorPalette: [String]
},
profileInfo: {
profilePreviewUrl: String,
fullName: String,
designation: String,
summary: String,
},
contactInfo: {
email: String,
phone: String,
location: String,
linkedin: String,
github: String,
website: String,
},
workExperience: [
{
company: String,
role: String,
startDate: String,
endDate: String,
description: String,
},
],
education: [
{
degree: String,
institution: String,
startDate: String,
endDate: String,
},
],
skills: [
{
name: String,
progress: Number,
},
],
projects: [
{
title: String,
description: String,
github: String,
liveDemo: String,
},
],
certifications: [
{
title: String,
issuer: String,
year: String,
},
],
languages: [
{
name: String,
progress: Number,
},
],
interests: [String],
},
{
timestamps: { createdAt: "createdAt", updatedAt: "updatedAt" },
}
);
module.exports = mongoose.model("Resume", ResumeSchema);

13
backend/models/User.js Normal file
View File

@ -0,0 +1,13 @@
const mongoose = require("mongoose");
const UserSchema = new mongoose.Schema(
{
name: { type: String, required: true },
email: { type: String, required: true, unique: true },
password: { type: String, required: true },
profileImageUrl: { type: String, default: null },
},
{ timestamps: true }
);
module.exports = mongoose.model("User", UserSchema);

1723
backend/package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

26
backend/package.json Normal file
View File

@ -0,0 +1,26 @@
{
"name": "resume-builder",
"version": "1.0.0",
"description": "",
"main": "server.js",
"scripts": {
"start": "node server.js",
"dev": "nodemon server.js"
},
"keywords": [],
"author": "",
"license": "ISC",
"type": "commonjs",
"dependencies": {
"bcryptjs": "^3.0.2",
"cors": "^2.8.5",
"dotenv": "^16.5.0",
"express": "^5.1.0",
"jsonwebtoken": "^9.0.2",
"mongoose": "^8.13.2",
"multer": "^1.4.5-lts.2"
},
"devDependencies": {
"nodemon": "^3.1.9"
}
}

View File

@ -0,0 +1,23 @@
const express = require("express");
const { registerUser, loginUser, getUserProfile } = require("../controllers/authController");
const { protect } = require("../middlewares/authMiddleware");
const upload = require('../middlewares/uploadMiddleware')
const router = express.Router();
// Auth Routes
router.post("/register", registerUser); // Register User
router.post("/login", loginUser); // Login User
router.get("/profile", protect, getUserProfile); // Get User Profile
router.post("/upload-image", upload.single("image"), (req, res) => {
if (!req.file) {
return res.status(400).json({ message: "No file uploaded" });
}
const imageUrl = `${req.protocol}://${req.get("host")}/uploads/${
req.file.filename
}`;
res.status(200).json({ imageUrl });
});
module.exports = router;

View File

@ -0,0 +1,23 @@
const express = require("express");
const {
createResume,
getUserResumes,
getResumeById,
updateResume,
deleteResume,
} = require("../controllers/resumeController");
const { protect } = require("../middlewares/authMiddleware");
const { uploadResumeImages } = require("../controllers/uploadImages");
const router = express.Router();
router.post("/", protect, createResume); // Create Resume
router.get("/", protect, getUserResumes); // Get Resume
router.get("/:id", protect, getResumeById); // Get Resume By ID
router.put("/:id", protect, updateResume); // Update Resume
router.put("/:id/upload-images", protect, uploadResumeImages);
router.delete("/:id", protect, deleteResume); // Delete Resume
module.exports = router;

45
backend/server.js Normal file
View File

@ -0,0 +1,45 @@
require("dotenv").config();
const express = require("express");
const cors = require("cors");
const path = require("path");
const connectDB = require('./config/db')
const authRoutes = require('./routes/authRoutes')
const resumeRoutes = require('./routes/resumeRoutes')
const app = express();
// Middleware to handle CORS
app.use(
cors({
origin: "*",
methods: ["GET", "POST", "PUT", "DELETE"],
allowedHeaders: ["Content-Type", "Authorization"],
})
);
// Connect Database
connectDB();
// Middleware
app.use(express.json());
// Routes
app.use("/api/auth", authRoutes);
app.use("/api/resume", resumeRoutes);
// Serve uploads folder
app.use(
"/uploads",
express.static(path.join(__dirname, "uploads"), {
setHeaders: (res, path) => {
res.set("Access-Control-Allow-Origin", "http://localhost:5173");
},
})
);
// Start Server
const PORT = process.env.PORT || 5000;
app.listen(PORT, () => console.log(`Server running on port ${PORT}`));

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 76 KiB