Add server.js

This commit is contained in:
2026-01-05 15:34:06 +05:30
commit a2d098fe87

743
server.js Normal file
View File

@@ -0,0 +1,743 @@
const express = require("express");
require('dotenv').config();
const cors = require("cors");
const nodemailer = require("nodemailer");
const multer = require("multer");
const fs = require('fs/promises'); // change this line
const path = require("path");
const { v4: uuid } = require('uuid');
const app = express();
const PORT = 8000;
const DATA_FILE = path.join(__dirname, "projects.json");
const JOBS_DATA_FILE = path.join(__dirname, "jobs.json");
const GALLERY_DATA_FILE = path.join(__dirname, "gallery.json");
app.use(cors());
app.use(express.json());
app.use("/uploads", express.static(path.join(__dirname, "uploads")));
app.use("/applications", express.static(path.join(__dirname, "applications")));
app.use("/gallery-media", express.static(path.join(__dirname, "gallery-media")));
const loadData = async (filePath) => {
try {
await fs.access(filePath); // check if file exists
const data = await fs.readFile(filePath, "utf-8");
return JSON.parse(data);
} catch {
return [];
}
};
const saveData = async (filePath, data) => {
await fs.writeFile(filePath, JSON.stringify(data, null, 2));
};
// ====================== GALLERY API ====================== //
const galleryStorage = multer.diskStorage({
destination: async (req, file, cb) => {
const dir = "./gallery-media";
fs.mkdir(dir, { recursive: true }) // async, safe
cb(null, dir);
},
});
const uploadGallery = multer({
storage: galleryStorage,
fileFilter: (req, file, cb) => {
const filetypes = /jpeg|jpg|png|gif|mp4|mov|avi/;
const extname = filetypes.test(path.extname(file.originalname).toLowerCase());
const mimetype = filetypes.test(file.mimetype);
if (extname && mimetype) {
return cb(null, true);
} else {
cb(new Error("Only images (JPEG, JPG, PNG, GIF) and videos (MP4, MOV, AVI) are allowed"));
}
},
limits: { fileSize: 500 * 1024 * 1024 } // 500MB limit
});
// Get all gallery items with filtering by category
app.get("/api/gallery", async (req, res) => {
const items = await loadData(GALLERY_DATA_FILE);
if (req.query.category) {
const filtered = items.filter(item => item.category === req.query.category);
return res.json(filtered);
}
res.json(items);
});
// Add new gallery item
app.post("/api/gallery", uploadGallery.single("media"), async (req, res) => {
const { category, caption, date } = req.body;
const mediaFile = req.file;
if (!category || !mediaFile) {
return res.status(400).json({ error: "Category and media file are required" });
}
const mediaType = mediaFile.mimetype.startsWith("video") ? "video" : "image";
const newItem = {
id: uuid(),
category,
caption: caption || "",
date: date || "",
type: mediaType,
url: `/gallery-media/${mediaFile.filename}`,
createdAt: new Date().toISOString()
};
const items = await loadData(GALLERY_DATA_FILE); // ✅ now safe
items.push(newItem);
await saveData(GALLERY_DATA_FILE, items); // also await
res.status(201).json({ message: "Gallery item added successfully", item: newItem });
});
// Update gallery item
app.put("/api/gallery/:id", uploadGallery.single("media"), async (req, res) => {
const itemId = req.params.id;
const { category, caption, date } = req.body;
const mediaFile = req.file;
let items = await loadData(GALLERY_DATA_FILE);
const itemIndex = items.findIndex(item => item.id === itemId);
if (itemIndex === -1) {
return res.status(404).json({ error: "Gallery item not found" });
}
const existingItem = items[itemIndex];
let mediaType = existingItem.type;
let mediaUrl = existingItem.url;
if (mediaFile) {
const oldFilename = existingItem.url.split("/").pop();
const oldPath = path.join(__dirname, "gallery-media", oldFilename);
if (await fs.stat(oldPath).catch(() => false)) {
await fs.unlink(oldPath);
}
mediaType = mediaFile.mimetype.startsWith("video") ? "video" : "image";
mediaUrl = `/gallery-media/${mediaFile.filename}`;
}
items[itemIndex] = {
...existingItem,
category: category || existingItem.category,
caption: caption || existingItem.caption,
date: date || existingItem.date,
type: mediaType,
url: mediaUrl,
updatedAt: new Date().toISOString()
};
await saveData(GALLERY_DATA_FILE, items);
res.json({ message: "Gallery item updated successfully", item: items[itemIndex] });
});
// Delete gallery item
app.delete("/api/gallery/:id", async (req, res) => {
const itemId = parseInt(req.params.id);
const items = await loadData(GALLERY_DATA_FILE);
const itemIndex = items.findIndex(item => item.id === itemId);
if (itemIndex === -1) {
return res.status(404).json({ error: "Gallery item not found" });
}
const filename = items[itemIndex].url.split("/").pop();
const filePath = path.join(__dirname, "gallery-media", filename);
if (await fs.stat(filePath).catch(() => false)) {
await fs.unlink(filePath);
}
items.splice(itemIndex, 1);
await saveData(GALLERY_DATA_FILE, items);
res.json({ message: "Gallery item deleted successfully" });
});
// ========= CONTACT FORM ========= //
app.post("/contact", async (req, res) => {
const { name, email, contact, message } = req.body;
// Validation
if (!name || !email || !contact || !message) {
return res.status(400).json({ success: false, error: "All fields are required" });
}
// Additional validation
if (name.length < 2) {
return res.status(400).json({ success: false, error: "Name must be at least 2 characters" });
}
if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email)) {
return res.status(400).json({ success: false, error: "Invalid email format" });
}
const digits = contact.replace(/\D/g, "");
if (digits.length < 7) {
return res.status(400).json({ success: false, error: "Contact number must have at least 7 digits" });
}
if (message.length < 10) {
return res.status(400).json({ success: false, error: "Message must be at least 10 characters" });
}
try {
// Use environment variables for email credentials
const emailUser = process.env.EMAIL_USER || "laxmibamnale2002@gmail.com";
const emailPass = process.env.EMAIL_PASS || "smqcwjwdsuiywrse";
if (!emailPass) {
console.error("Email password not configured");
return res.status(500).json({ success: false, message: "Server configuration error" });
}
const transporter = nodemailer.createTransport({
service: "gmail",
auth: {
user: process.env.EMAIL_USER,
pass: process.env.EMAIL_PASS,
},
});
// Verify connection configuration
await transporter.verify();
const mailToOwner = {
from: emailUser, // Use your email as from address to avoid authentication issues
replyTo: email, // Set reply-to to customer's email
to: emailUser,
subject: `New Contact Form Submission from ${name}`,
html: `
<h3>New Contact Form Submission</h3>
<p><strong>Name:</strong> ${name}</p>
<p><strong>Email:</strong> ${email}</p>
<p><strong>Contact:</strong> ${contact}</p>
<p><strong>Message:</strong></p>
<p>${message.replace(/\n/g, "<br>")}</p>
<p><em>Received on: ${new Date().toLocaleString()}</em></p>
`,
};
await transporter.sendMail(mailToOwner);
const autoReply = {
from: emailUser,
to: email,
subject: "Thank you for contacting Laxmi Civil Engineering Services!",
html: `
<div style="font-family: Arial, sans-serif; max-width: 600px; margin: 0 auto;">
<h2 style="color: #0056b3;">Thank You for Contacting Us!</h2>
<p>Dear ${name},</p>
<p>Thank you for reaching out to Laxmi Civil Engineering Services Pvt. Ltd. We have received your message and will get back to you within 24-48 hours.</p>
<p>For urgent inquiries, please call us at <strong>0231-2521554</strong> or <strong>0231-2683900</strong>.</p>
<p><strong>Summary of your message:</strong></p>
<blockquote style="background: #f9f9f9; padding: 10px; border-left: 4px solid #ccc;">
${message.replace(/\n/g, "<br>")}
</blockquote>
<p>Best regards,<br/>Team Laxmi Civil Engineering Services</p>
<hr style="border: none; border-top: 1px solid #eee; margin: 20px 0;">
<p style="font-size: 12px; color: #777;">
Laxmi Civil Engineering Services Pvt. Ltd.<br>
1148, E. Sykes Extension, Kolhapur 416 001, Maharashtra, India.<br>
Phone: 0231-2521554, 2683900 | Email: laxmibamnale2002@gmail.com
</p>
</div>
`,
};
await transporter.sendMail(autoReply);
res.status(200).json({ success: true, message: "Message sent successfully" });
} catch (err) {
console.error("Error sending email:", err);
if (err.code === "EAUTH") {
res.status(500).json({ success: false, message: "Email authentication failed" });
} else if (err.code === "EENVELOPE") {
res.status(400).json({ success: false, message: "Invalid email address" });
} else {
res.status(500).json({ success: false, message: "Server error while sending email" });
}
}
});
// ========= PROJECT UPLOAD ========= //
const storage = multer.diskStorage({
destination: (req, file, cb) => {
const dir = "./uploads";
fs.mkdir(dir, { recursive: true }) // creates dir if not exists
cb(null, dir);
},
filename: (req, file, cb) => {
const uniqueName = Date.now() + "-" + file.originalname;
cb(null, uniqueName);
},
});
const upload = multer({ storage });
const loadProjects = async () => {
try {
await fs.access(DATA_FILE);
const data = await fs.readFile(DATA_FILE, "utf-8");
return JSON.parse(data);
} catch {
return [];
}
};
const saveProjects = async (projects) => {
await fs.writeFile(DATA_FILE, JSON.stringify(projects, null, 2));
};
app.post("/api/projects", upload.single("image"), async (req, res) => {
const { sector } = req.body;
const image = req.file ? `/uploads/${req.file.filename}` : "";
if (!sector || !image) {
return res.status(400).json({ error: "Sector and image are required" });
}
const newProject = { id: uuid(), sector, image };
const projects = await loadProjects(); // ✅ await
projects.push(newProject);
await saveProjects(projects); // ✅ await
res.status(201).json({ message: "Project added successfully", project: newProject });
});
app.get("/api/projects", async (req, res) => {
const projects = await loadProjects(); // ✅ await
res.json(projects);
});
app.post("/api/projects/update/:id", upload.single("image"), (req, res) => {
const projectId = req.params.id;
const { sector } = req.body;
let projects = loadProjects();
const projectIndex = projects.findIndex((p) => p.id === projectId);
if (projectIndex === -1) {
return res.status(404).json({ error: "Project not found" });
}
const existingProject = projects[projectIndex];
projects[projectIndex] = {
...existingProject,
sector,
image: req.file ? `/uploads/${req.file.filename}` : existingProject.image,
};
saveProjects(projects);
res.json({ message: "Project updated successfully", project: projects[projectIndex] });
});
app.delete("/api/projects/:id", (req, res) => {
const projectId = parseInt(req.params.id);
let projects = loadProjects();
const updatedProjects = projects.filter((project) => project.id !== projectId);
if (projects.length === updatedProjects.length) {
return res.status(404).json({ error: "Project not found" });
}
saveProjects(updatedProjects);
res.json({ message: "Project deleted successfully" });
});
// ========= JOB APPLICATION UPLOAD ========= //
const applicationStorage = multer.diskStorage({
destination: (req, file, cb) => {
const dir = "./applications";
fs.mkdir(dir, { recursive: true }) // creates dir if not exists
cb(null, dir);
},
filename: (req, file, cb) => {
const uniqueName = Date.now() + "-" + file.originalname;
cb(null, uniqueName);
},
});
const uploadApplication = multer({ storage: applicationStorage });
app.post("/send-application", uploadApplication.single("resume"), async (req, res) => {
const {
fullName,
email,
phone,
address,
education,
skill,
interest,
totalExperience,
expectedSalary,
currentCompany,
currentDesignation,
} = req.body;
if (!fullName || !email || !phone || !education || !skill || !totalExperience || !req.file) {
return res.status(400).json({ success: false, message: "Missing required fields" });
}
const resumePath = path.join(__dirname, "applications", req.file.filename);
try {
const transporter = nodemailer.createTransport({
service: "gmail",
auth: {
user: process.env.EMAIL_USER,
pass: process.env.EMAIL_PASS,
},
});
const mailOptions = {
from: email,
to: "laxmibamnale2002@gmail.com",
subject: `New Job Application from ${fullName}`,
text: `Full Name: ${fullName}
Email: ${email}
Phone: ${phone}
Address: ${address}
Education: ${education}
Skill: ${skill}
Interest: ${interest}
Experience: ${totalExperience}
Expected Salary: ${expectedSalary}
Current Company: ${currentCompany}
Current Designation: ${currentDesignation}`,
attachments: [
{
filename: req.file.originalname,
path: resumePath,
},
],
};
await transporter.sendMail(mailOptions);
const autoReply = {
from: "laxmibamnale2002@gmail.com",
to: email,
subject: "Application Received - Laxmi Civil Engineering Services",
text: `Dear ${fullName},
Thank you for applying to Laxmi Civil Engineering Services Pvt. Ltd.
We have received your application and resume. Our HR team will review your profile and get back to you shortly if shortlisted.
Best regards,
Laxmi Civil Engineering Services Pvt. Ltd.`,
};
await transporter.sendMail(autoReply);
res.status(200).json({ success: true, message: "Application submitted successfully" });
} catch (err) {
console.error("Error submitting application:", err);
res.status(500).json({ success: false, message: "Server error" });
}
});
// ========= ENHANCED JOB POSTINGS API ========= //
const loadJobs = async () => {
try {
await fs.access(JOBS_DATA_FILE);
const data = await fs.readFile(JOBS_DATA_FILE, "utf-8");
return JSON.parse(data);
} catch {
return [];
}
};
const saveJobs = async (jobs) => {
await fs.writeFile(JOBS_DATA_FILE, JSON.stringify(jobs, null, 2));
};
// Get all jobs with automatic status updates
app.get("/api/jobs", async (req, res) => {
let jobs = await loadJobs(); // ✅ await
const currentDate = new Date();
jobs = jobs.map(job => {
if (job.closingDate && new Date(job.closingDate) < currentDate) {
return { ...job, isActive: false };
}
return job;
});
await saveJobs(jobs); // ✅ await
res.json(jobs);
});
// Create new job posting with enhanced fields
app.post("/api/jobs", (req, res) => {
const {
positionName,
qualification,
experience,
location,
skills,
numberOfOpenings,
jobDescription,
postingDate,
closingDate,
isActive = true
} = req.body;
if (
!positionName ||
!qualification ||
!experience ||
!location ||
!Array.isArray(skills) ||
skills.length === 0 ||
!numberOfOpenings ||
!jobDescription ||
!postingDate
) {
return res.status(400).json({ error: "Missing required fields" });
}
const jobs = loadJobs();
const newJob = {
id: uuid(),
positionName,
qualification,
experience,
location,
skills,
numberOfOpenings,
jobDescription,
postingDate,
closingDate: closingDate || null,
isActive: closingDate ? new Date(closingDate) >= new Date() : isActive
};
jobs.push(newJob);
saveJobs(jobs);
res.status(201).json({ message: "Job created", job: newJob });
});
// Update existing job posting with enhanced fields
app.put("/api/jobs/:id", (req, res) => {
const jobId = parseInt(req.params.id);
const {
positionName,
qualification,
experience,
location,
skills,
numberOfOpenings,
jobDescription,
postingDate,
closingDate,
isActive
} = req.body;
const jobs = loadJobs();
const index = jobs.findIndex((job) => job.id === jobId);
if (index === -1) {
return res.status(404).json({ error: "Job not found" });
}
if (
!positionName ||
!qualification ||
!experience ||
!location ||
!Array.isArray(skills) ||
skills.length === 0 ||
!numberOfOpenings ||
!jobDescription ||
!postingDate
) {
return res.status(400).json({ error: "Missing required fields" });
}
const currentDate = new Date();
const closingDateObj = closingDate ? new Date(closingDate) : null;
const actualIsActive = closingDateObj ? closingDateObj >= currentDate : isActive;
jobs[index] = {
id: jobId,
positionName,
qualification,
experience,
location,
skills,
numberOfOpenings,
jobDescription,
postingDate,
closingDate: closingDate || null,
isActive: actualIsActive
};
saveJobs(jobs);
res.json({ message: "Job updated", job: jobs[index] });
});
// Toggle job active status
app.patch("/api/jobs/:id/toggle-active", (req, res) => {
const jobId = parseInt(req.params.id);
const jobs = loadJobs();
const index = jobs.findIndex((job) => job.id === jobId);
if (index === -1) {
return res.status(404).json({ error: "Job not found" });
}
// Don't allow toggling if there's a closing date in the past
const currentDate = new Date();
if (jobs[index].closingDate && new Date(jobs[index].closingDate) < currentDate) {
return res.status(400).json({ error: "Cannot activate job with expired closing date" });
}
jobs[index].isActive = !jobs[index].isActive;
saveJobs(jobs);
res.json({ message: "Job status updated", job: jobs[index] });
});
// Delete job posting
app.delete("/api/jobs/:id", (req, res) => {
const jobId = parseInt(req.params.id);
let jobs = loadJobs();
const initialLength = jobs.length;
jobs = jobs.filter((job) => job.id !== jobId);
if (jobs.length === initialLength) {
return res.status(404).json({ error: "Job not found" });
}
saveJobs(jobs);
res.json({ message: "Job deleted" });
});
// ========= CAREER CONTACT FORM ========= //
const careerStorage = multer.diskStorage({
destination: (req, file, cb) => {
const dir = "./career-applications";
fs.mkdir(dir, { recursive: true }) // creates dir if not exists
cb(null, dir);
},
filename: (req, file, cb) => {
const uniqueName = Date.now() + "-" + file.originalname;
cb(null, uniqueName);
},
});
const uploadCareer = multer({
storage: careerStorage,
limits: { fileSize: 10 * 1024 * 1024 },
fileFilter: (req, file, cb) => {
const allowedExt = ['.pdf', '.doc', '.docx'];
const ext = path.extname(file.originalname).toLowerCase();
if (allowedExt.includes(ext)) {
cb(null, true);
} else {
cb(new Error('Only PDF/DOC/DOCX files are allowed'));
}
}
});
app.post('/api/careers/contact', uploadCareer.single('resume'), async (req, res) => {
try {
const { fullName, email, phone } = req.body;
const resumeFile = req.file;
if (!fullName || !email || !resumeFile) {
return res.status(400).json({ success: false, error: 'Missing required fields or resume.' });
}
const transporter = nodemailer.createTransport({
service: "gmail",
auth: {
user: process.env.EMAIL_USER,
pass: process.env.EMAIL_PASS,
},
});
const mailOptions = {
from: '"Career Contact Form" <laxmibamnale2002@gmail.com>',
to: "laxmibamnale2002@gmail.com",
subject: `New Career Contact from ${fullName}`,
text: `
You have received a new career contact form submission.
Full Name: ${fullName}
Email: ${email}
Phone: ${phone || 'Not provided'}
Resume is attached.
`,
attachments: [
{
filename: resumeFile.originalname,
path: resumeFile.path,
},
],
};
await transporter.sendMail(mailOptions);
const autoReply = {
from: "<laxmibamnale2002@gmail.com>",
to: email,
subject: "Thank you for contacting Laxmi Civil Engineering Services",
text: `Dear ${fullName},
Thank you for reaching out to us via the Career Contact form. We have received your details and resume.
Our HR team will review and get back to you soon.
Best regards,
Laxmi Civil Engineering Services Pvt. Ltd.`,
};
await transporter.sendMail(autoReply);
fs.unlink(resumeFile.path, (err) => {
if (err) console.error('Error deleting uploaded resume:', err);
});
res.json({ success: true, message: 'Career contact form submitted successfully.' });
} catch (error) {
console.error('Error in /api/careers/contact:', error);
res.status(500).json({ success: false, message: 'Server error' });
}
});
// ========= START SERVER ========= //
app.listen(PORT, "0.0.0.0", () => {
console.log(`Server running on http://0.0.0.0:${PORT}`);
});