Initial commit
This commit is contained in:
3
.env
Normal file
3
.env
Normal file
@@ -0,0 +1,3 @@
|
||||
EMAIL_USER=laxmibamnale2002@gmail.com
|
||||
EMAIL_PASS=smqcwjwdsuiywrse
|
||||
|
||||
3
.gitattributes
vendored
Normal file
3
.gitattributes
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
*.xlsx filter=lfs diff=lfs merge=lfs -text
|
||||
*.zip filter=lfs diff=lfs merge=lfs -text
|
||||
*.pdf filter=lfs diff=lfs merge=lfs -text
|
||||
3
.gitignore
vendored
Normal file
3
.gitignore
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
node_modules/
|
||||
*.log
|
||||
dist/
|
||||
3
contacts.json
Normal file
3
contacts.json
Normal file
@@ -0,0 +1,3 @@
|
||||
[
|
||||
|
||||
]
|
||||
65
gallery.json
Normal file
65
gallery.json
Normal file
@@ -0,0 +1,65 @@
|
||||
[
|
||||
{
|
||||
"id": 1755343085723,
|
||||
"category": "Awards & Achievements",
|
||||
"caption": "MKVDC Pune Sanmanpatra dated",
|
||||
"date": "",
|
||||
"type": "image",
|
||||
"url": "/gallery-media/1755343085623-cert1.jpg",
|
||||
"createdAt": "2025-08-16T11:18:05.723Z"
|
||||
},
|
||||
{
|
||||
"id": 1755343138070,
|
||||
"category": "Awards & Achievements",
|
||||
"caption": "Ambarnath - Kulgaon Badlapur Barrage certificate",
|
||||
"date": "",
|
||||
"type": "image",
|
||||
"url": "/gallery-media/1755343137920-cert2.jpg",
|
||||
"createdAt": "2025-08-16T11:18:58.070Z"
|
||||
},
|
||||
{
|
||||
"id": 1755343165414,
|
||||
"category": "Awards & Achievements",
|
||||
"caption": "American Water Works Association Certificate",
|
||||
"date": "",
|
||||
"type": "image",
|
||||
"url": "/gallery-media/1755343165376-cert3.jpg",
|
||||
"createdAt": "2025-08-16T11:19:25.414Z"
|
||||
},
|
||||
{
|
||||
"id": 1755343199851,
|
||||
"category": "Awards & Achievements",
|
||||
"caption": "Kerala Water Authority - Letter of Appreciation",
|
||||
"date": "",
|
||||
"type": "image",
|
||||
"url": "/gallery-media/1755343199753-cert4.jpg",
|
||||
"createdAt": "2025-08-16T11:19:59.851Z"
|
||||
},
|
||||
{
|
||||
"id": 1755343250163,
|
||||
"category": "Awards & Achievements",
|
||||
"caption": "wilo evaluation award 2024",
|
||||
"date": "",
|
||||
"type": "image",
|
||||
"url": "/gallery-media/1755343250133-WhatsApp Image 2025-08-09 at 12.17.33 PM.jpeg",
|
||||
"createdAt": "2025-08-16T11:20:50.163Z"
|
||||
},
|
||||
{
|
||||
"id": 1755764880336,
|
||||
"category": "Awards & Achievements",
|
||||
"caption": "",
|
||||
"date": "",
|
||||
"type": "image",
|
||||
"url": "/gallery-media/1755764880241-WhatsApp Image 2023-09-27 at 5.04.05 PM.jpeg",
|
||||
"createdAt": "2025-08-21T08:28:00.336Z"
|
||||
},
|
||||
{
|
||||
"id": 1762603729920,
|
||||
"category": "Awards & Achievements",
|
||||
"caption": "CERTIFICATE OF APPRECIATION from HON. CHIEF MINISTER OF GOA for JJM Work successfully completed and commissioned",
|
||||
"date": "",
|
||||
"type": "image",
|
||||
"url": "/gallery-media/1762603729917-CERTIFICATE OF APPRECIATION from HON. CHIEF MINISTER OF GOA for JJM Work successfully completed and commissioned.png",
|
||||
"createdAt": "2025-11-08T12:08:49.920Z"
|
||||
}
|
||||
]
|
||||
71
jobs.json
Normal file
71
jobs.json
Normal file
@@ -0,0 +1,71 @@
|
||||
[
|
||||
{
|
||||
"id": 1755683797304,
|
||||
"positionName": "Software Developer",
|
||||
"qualification": "B.Tech",
|
||||
"experience": "3-5",
|
||||
"location": "Pune",
|
||||
"skills": [
|
||||
"Java",
|
||||
"Python",
|
||||
"JavaScript"
|
||||
],
|
||||
"salary": "5-6",
|
||||
"numberOfOpenings": "5",
|
||||
"jobDescription": "OPEN POSITION FOR SOFTWARE DEVELOPER",
|
||||
"postingDate": "2025-08-20",
|
||||
"closingDate": "2025-08-26",
|
||||
"isActive": false
|
||||
},
|
||||
{
|
||||
"id": 1756120986070,
|
||||
"positionName": "Assistant Project Manager",
|
||||
"qualification": "MBA",
|
||||
"experience": "4-5",
|
||||
"location": "pune",
|
||||
"skills": [
|
||||
"Pipeline Construction",
|
||||
"Budgeting",
|
||||
"Team Leadership"
|
||||
],
|
||||
"salary": "7-8",
|
||||
"numberOfOpenings": "4",
|
||||
"jobDescription": "hello",
|
||||
"postingDate": "2025-08-25",
|
||||
"closingDate": "2025-08-31",
|
||||
"isActive": false
|
||||
},
|
||||
{
|
||||
"id": 1759213958713,
|
||||
"positionName": "Software Developer",
|
||||
"qualification": "BTech",
|
||||
"experience": "1",
|
||||
"location": "Pune",
|
||||
"skills": [
|
||||
"SQL"
|
||||
],
|
||||
"salary": "20,000-30,000",
|
||||
"numberOfOpenings": "2",
|
||||
"jobDescription": "Experienced",
|
||||
"postingDate": "2025-09-30",
|
||||
"closingDate": null,
|
||||
"isActive": true
|
||||
},
|
||||
{
|
||||
"id": 1765176546024,
|
||||
"positionName": "Software Developer",
|
||||
"qualification": "BTech",
|
||||
"experience": "1",
|
||||
"location": "Pune",
|
||||
"skills": [
|
||||
"Python",
|
||||
"java"
|
||||
],
|
||||
"salary": "60k - 65k",
|
||||
"numberOfOpenings": "4",
|
||||
"jobDescription": "test",
|
||||
"postingDate": "2025-12-08",
|
||||
"closingDate": null,
|
||||
"isActive": true
|
||||
}
|
||||
]
|
||||
1430
package-lock.json
generated
Normal file
1430
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
20
package.json
Normal file
20
package.json
Normal file
@@ -0,0 +1,20 @@
|
||||
{
|
||||
"name": "lcepl-backend",
|
||||
"version": "1.0.0",
|
||||
"main": "server.js",
|
||||
"scripts": {
|
||||
"dev": "nodemon server.js"
|
||||
},
|
||||
"dependencies": {
|
||||
"body-parser": "^1.20.3",
|
||||
"cors": "^2.8.5",
|
||||
"dotenv": "^17.2.1",
|
||||
"express": "^4.21.2",
|
||||
"fs": "^0.0.1-security",
|
||||
"multer": "^2.0.2",
|
||||
"nodemailer": "^7.0.5"
|
||||
},
|
||||
"devDependencies": {
|
||||
"nodemon": "^3.1.0"
|
||||
}
|
||||
}
|
||||
132
projects.json
Normal file
132
projects.json
Normal file
@@ -0,0 +1,132 @@
|
||||
[
|
||||
{
|
||||
"id": 1754996200535,
|
||||
"sector": "electromechanical",
|
||||
"image": "/uploads/1754996200514-e811b15b-9d53-4c92-b2b0-280c724bec3b.jpg"
|
||||
},
|
||||
{
|
||||
"id": 1754996228903,
|
||||
"sector": "renewable energy",
|
||||
"image": "/uploads/1754996228890-2a9cc942-939e-4b2a-8ed8-6fdb99cbe919.jpg"
|
||||
},
|
||||
{
|
||||
"id": 1754996248901,
|
||||
"sector": "roads",
|
||||
"image": "/uploads/1754996248875-0e10027f-7b22-46c3-82d0-fd25ed88cd37.jpg"
|
||||
},
|
||||
{
|
||||
"id": 1754996269954,
|
||||
"sector": "storm water management",
|
||||
"image": "/uploads/1754996269898-WhatsApp Image 2025-08-12 at 12.53.21 PM.jpeg"
|
||||
},
|
||||
{
|
||||
"id": 1754996282054,
|
||||
"sector": "storm water management",
|
||||
"image": "/uploads/1754996282022-WhatsApp Image 2025-08-12 at 12.53.21 PM (1).jpeg"
|
||||
},
|
||||
{
|
||||
"id": 1754996302879,
|
||||
"sector": "tunnel",
|
||||
"image": "/uploads/1754996302853-WhatsApp Image 2025-08-12 at 12.53.15 PM (1).jpeg"
|
||||
},
|
||||
{
|
||||
"id": 1754996311766,
|
||||
"sector": "tunnel",
|
||||
"image": "/uploads/1754996311723-WhatsApp Image 2025-08-12 at 12.53.15 PM.jpeg"
|
||||
},
|
||||
{
|
||||
"id": 1754996330787,
|
||||
"sector": "tunnel",
|
||||
"image": "/uploads/1754996330755-WhatsApp Image 2025-08-12 at 12.53.18 PM (1).jpeg"
|
||||
},
|
||||
{
|
||||
"id": 1754996358966,
|
||||
"sector": "water supply",
|
||||
"image": "/uploads/1754996358949-WhatsApp Image 2025-08-09 at 12.30.50 PM (1).jpeg"
|
||||
},
|
||||
{
|
||||
"id": 1755338545762,
|
||||
"sector": "water supply",
|
||||
"image": "/uploads/1755338545753-elevated_service_res_p4PJo.5e769222e35913528376.jpg"
|
||||
},
|
||||
{
|
||||
"id": 1755338571673,
|
||||
"sector": "wastewater / sewerage works",
|
||||
"image": "/uploads/1755338571628-Sewage-WT1.c1c508759572132379bf (1).jpg"
|
||||
},
|
||||
{
|
||||
"id": 1755338589282,
|
||||
"sector": "water supply",
|
||||
"image": "/uploads/1755338589273-Jack-wells.ae632a5749ccfb960389.jpg"
|
||||
},
|
||||
{
|
||||
"id": 1755338632351,
|
||||
"sector": "wastewater / sewerage works",
|
||||
"image": "/uploads/1755338632341-Non-conventional-water-treatment-plants-.e452887dc76a9c77aa01.jpg"
|
||||
},
|
||||
{
|
||||
"id": 1755339600178,
|
||||
"sector": "water supply",
|
||||
"image": "/uploads/1755339600135-water_treatment_plan_QDiP2.e7a8e82ec7e3a4e3c148.jpg"
|
||||
},
|
||||
{
|
||||
"id": 1755511923263,
|
||||
"sector": "tunnel",
|
||||
"image": "/uploads/1755511923123-WhatsApp Image 2025-08-18 at 3.25.13 PM.jpeg"
|
||||
},
|
||||
{
|
||||
"id": 1755511932847,
|
||||
"sector": "tunnel",
|
||||
"image": "/uploads/1755511932814-WhatsApp Image 2025-08-18 at 2.24.17 PM.jpeg"
|
||||
},
|
||||
{
|
||||
"id": 1755670485719,
|
||||
"sector": "wastewater / sewerage works",
|
||||
"image": "/uploads/1755670485689-Picture1.jpg"
|
||||
},
|
||||
{
|
||||
"id": 1755670494520,
|
||||
"sector": "wastewater / sewerage works",
|
||||
"image": "/uploads/1755670494409-Picture2.png"
|
||||
},
|
||||
{
|
||||
"id": 1755670504975,
|
||||
"sector": "wastewater / sewerage works",
|
||||
"image": "/uploads/1755670504954-Picture3.jpg"
|
||||
},
|
||||
{
|
||||
"id": 1755673960257,
|
||||
"sector": "wastewater / sewerage works",
|
||||
"image": "/uploads/1755673960238-Picture4.png"
|
||||
},
|
||||
{
|
||||
"id": 1755673968617,
|
||||
"sector": "wastewater / sewerage works",
|
||||
"image": "/uploads/1755673968590-Picture5.png"
|
||||
},
|
||||
{
|
||||
"id": 1755673977458,
|
||||
"sector": "wastewater / sewerage works",
|
||||
"image": "/uploads/1755673977434-Picture6.png"
|
||||
},
|
||||
{
|
||||
"id": 1755674126735,
|
||||
"sector": "electromechanical",
|
||||
"image": "/uploads/1755674126708-image (2).jpg"
|
||||
},
|
||||
{
|
||||
"id": 1755764796849,
|
||||
"sector": "real estate / buildings",
|
||||
"image": "/uploads/1755764796583-04da8a50-98a3-4516-86de-e8f8f46594b8.jpg"
|
||||
},
|
||||
{
|
||||
"id": 1762768071860,
|
||||
"sector": "real estate / buildings",
|
||||
"image": "/uploads/1762768071841-Gemini_Generated_Image_l7v50ml7v50ml7v5.png"
|
||||
},
|
||||
{
|
||||
"id": 1762768163950,
|
||||
"sector": "wastewater / sewerage works",
|
||||
"image": "/uploads/1762768163944-WhatsApp Image 2025-11-10 at 3.18.33 PM.jpeg"
|
||||
}
|
||||
]
|
||||
8
routes/contactRoutes.js
Normal file
8
routes/contactRoutes.js
Normal file
@@ -0,0 +1,8 @@
|
||||
const express = require('express');
|
||||
const router = express.Router();
|
||||
const { saveContact } = require('../controllers/contactController');
|
||||
|
||||
// ✅ Use `router.post`, NOT `app.post`
|
||||
router.post('/', saveContact);
|
||||
|
||||
module.exports = router;
|
||||
749
server.js
Normal file
749
server.js
Normal file
@@ -0,0 +1,749 @@
|
||||
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: `
|
||||
<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";
|
||||
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" <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}`);
|
||||
});
|
||||
15
utils/fileHandler.js
Normal file
15
utils/fileHandler.js
Normal file
@@ -0,0 +1,15 @@
|
||||
const fs = require('fs').promises;
|
||||
const path = require('path');
|
||||
|
||||
const dataPath = path.join(__dirname, '../data/applications.json');
|
||||
|
||||
const readApplications = async () => {
|
||||
const data = await fs.readFile(dataPath, 'utf-8');
|
||||
return JSON.parse(data);
|
||||
};
|
||||
|
||||
const writeApplications = async (data) => {
|
||||
await fs.writeFile(dataPath, JSON.stringify(data, null, 2), 'utf-8');
|
||||
};
|
||||
|
||||
module.exports = { readApplications, writeApplications };
|
||||
Reference in New Issue
Block a user