const express = require("express"); const cors = require("cors"); const nodemailer = require("nodemailer"); const multer = require("multer"); const fs = require("fs"); const path = require("path"); 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 = (filePath) => { if (!fs.existsSync(filePath)) return []; const data = fs.readFileSync(filePath, "utf-8"); try { return JSON.parse(data); } catch { return []; } }; const saveData = (filePath, data) => { fs.writeFileSync(filePath, JSON.stringify(data, null, 2)); }; // ====================== GALLERY API ====================== // const galleryStorage = multer.diskStorage({ destination: (req, file, cb) => { const dir = "./gallery-media"; if (!fs.existsSync(dir)) fs.mkdirSync(dir); cb(null, dir); }, filename: (req, file, cb) => { const uniqueName = Date.now() + "-" + file.originalname; cb(null, uniqueName); }, }); 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", (req, res) => { const items = 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"), (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: Date.now(), category, caption: caption || "", date: date || "", type: mediaType, url: `/gallery-media/${mediaFile.filename}`, createdAt: new Date().toISOString() }; const items = loadData(GALLERY_DATA_FILE); items.push(newItem); saveData(GALLERY_DATA_FILE, items); res.status(201).json({ message: "Gallery item added successfully", item: newItem }); }); // Update gallery item app.put("/api/gallery/:id", uploadGallery.single("media"), (req, res) => { const itemId = parseInt(req.params.id); const { category, caption, date } = req.body; const mediaFile = req.file; let items = 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) { // Delete old media file const oldFilename = existingItem.url.split("/").pop(); const oldPath = path.join(__dirname, "gallery-media", oldFilename); if (fs.existsSync(oldPath)) { fs.unlinkSync(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() }; saveData(GALLERY_DATA_FILE, items); res.json({ message: "Gallery item updated successfully", item: items[itemIndex] }); }); // Delete gallery item app.delete("/api/gallery/:id", (req, res) => { const itemId = parseInt(req.params.id); const items = 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" }); } // Delete associated media file const filename = items[itemIndex].url.split("/").pop(); const filePath = path.join(__dirname, "gallery-media", filename); if (fs.existsSync(filePath)) { fs.unlinkSync(filePath); } items.splice(itemIndex, 1); 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: emailUser, pass: emailPass, }, }); // 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: `

New Contact Form Submission

Name: ${name}

Email: ${email}

Contact: ${contact}

Message:

${message.replace(/\n/g, "
")}

Received on: ${new Date().toLocaleString()}

`, }; await transporter.sendMail(mailToOwner); const autoReply = { from: emailUser, to: email, subject: "Thank you for contacting Laxmi Civil Engineering Services!", html: `

Thank You for Contacting Us!

Dear ${name},

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.

For urgent inquiries, please call us at 0231-2521554 or 0231-2683900.

Summary of your message:

${message.replace(/\n/g, "
")}

Best regards,
Team Laxmi Civil Engineering Services


Laxmi Civil Engineering Services Pvt. Ltd.
1148, E. Sykes Extension, Kolhapur 416 001, Maharashtra, India.
Phone: 0231-2521554, 2683900 | Email: laxmibamnale2002@gmail.com

`, }; 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"; if (!fs.existsSync(dir)) fs.mkdirSync(dir); cb(null, dir); }, filename: (req, file, cb) => { const uniqueName = Date.now() + "-" + file.originalname; cb(null, uniqueName); }, }); const upload = multer({ storage }); const loadProjects = () => { if (!fs.existsSync(DATA_FILE)) return []; const data = fs.readFileSync(DATA_FILE, "utf-8"); try { return JSON.parse(data); } catch { return []; } }; const saveProjects = (projects) => { fs.writeFileSync(DATA_FILE, JSON.stringify(projects, null, 2)); }; app.post("/api/projects", upload.single("image"), (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: Date.now(), sector, image, }; const projects = loadProjects(); projects.push(newProject); saveProjects(projects); res.status(201).json({ message: "Project added successfully", project: newProject }); }); app.get("/api/projects", (req, res) => { const projects = loadProjects(); res.json(projects); }); app.post("/api/projects/update/:id", upload.single("image"), (req, res) => { const projectId = parseInt(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"; if (!fs.existsSync(dir)) fs.mkdirSync(dir); 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: "laxmibamnale2002@gmail.com", pass: "smqcwjwdsuiywrse", }, }); 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 = () => { if (!fs.existsSync(JOBS_DATA_FILE)) return []; const data = fs.readFileSync(JOBS_DATA_FILE, "utf-8"); try { return JSON.parse(data); } catch { return []; } }; const saveJobs = (jobs) => { fs.writeFileSync(JOBS_DATA_FILE, JSON.stringify(jobs, null, 2)); }; // Get all jobs with automatic status updates app.get("/api/jobs", (req, res) => { let jobs = loadJobs(); const currentDate = new Date(); // Update job statuses based on closing date jobs = jobs.map(job => { if (job.closingDate && new Date(job.closingDate) < currentDate) { return { ...job, isActive: false }; } return job; }); // Save updated statuses if any changed saveJobs(jobs); res.json(jobs); }); // Create new job posting with enhanced fields app.post("/api/jobs", (req, res) => { const { positionName, qualification, experience, location, skills, salary, numberOfOpenings, jobDescription, postingDate, closingDate, isActive = true } = req.body; if ( !positionName || !qualification || !experience || !location || !Array.isArray(skills) || skills.length === 0 || !salary || !numberOfOpenings || !jobDescription || !postingDate ) { return res.status(400).json({ error: "Missing required fields" }); } const jobs = loadJobs(); const newJob = { id: Date.now(), positionName, qualification, experience, location, skills, salary, 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, salary, 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 || !salary || !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, salary, 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"; if (!fs.existsSync(dir)) fs.mkdirSync(dir); 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: "laxmibamnale2002@gmail.com", pass: "smqcwjwdsuiywrse", }, }); const mailOptions = { from: '"Career Contact Form" ', 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: "", 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}`); });