From a2d098fe87c3ba95435415ebb21a84a409abb647 Mon Sep 17 00:00:00 2001 From: LaxmiB Date: Mon, 5 Jan 2026 15:34:06 +0530 Subject: [PATCH] Add server.js --- server.js | 743 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 743 insertions(+) create mode 100644 server.js diff --git a/server.js b/server.js new file mode 100644 index 0000000..270f357 --- /dev/null +++ b/server.js @@ -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: ` +

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"; + 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" ', + 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}`); +});