first commit
This commit is contained in:
parent
e7365b06ac
commit
a9e21cbe3c
9 changed files with 874 additions and 320 deletions
305
backend/index.js
305
backend/index.js
|
|
@ -114,214 +114,7 @@ app.post("/api/bookings", async (req, res) => {
|
|||
}
|
||||
});
|
||||
|
||||
// Pricing Engine & Car Data
|
||||
const VEHICLES = [
|
||||
// LUXURY TIER
|
||||
{
|
||||
id: 1,
|
||||
name: "Audi A6 Premium",
|
||||
type: "LUXURY",
|
||||
seats: "4+1",
|
||||
luggage: "2 Bags",
|
||||
image: "audi_a6",
|
||||
baseFarePerDay: 12000,
|
||||
driverAllowancePerDay: 600,
|
||||
extraKmRate: 45,
|
||||
includedKmPerDay: 250,
|
||||
badge: "Executive",
|
||||
features: ["Leather Interior", "Dual Climate", "Premium Audio", "Sunroof"]
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
name: "BMW 7 Series",
|
||||
type: "LUXURY",
|
||||
seats: "4+1",
|
||||
luggage: "3 Bags",
|
||||
image: "bmw_7_series",
|
||||
baseFarePerDay: 25000,
|
||||
driverAllowancePerDay: 800,
|
||||
extraKmRate: 85,
|
||||
includedKmPerDay: 200,
|
||||
badge: "VVIP Choice",
|
||||
features: ["Rear-seat Entertainment", "Massage Seats", "Quiet Cabin", "Chauffeur Driven"]
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
name: "Mercedes S-Class",
|
||||
type: "LUXURY",
|
||||
seats: "4+1",
|
||||
luggage: "2 Bags",
|
||||
image: "mercedes_s_class",
|
||||
baseFarePerDay: 28000,
|
||||
driverAllowancePerDay: 800,
|
||||
extraKmRate: 90,
|
||||
includedKmPerDay: 200,
|
||||
badge: "Ultimate Luxury",
|
||||
features: ["Ambient Lighting", "Burmester Sound", "Premium Chauffeur"]
|
||||
},
|
||||
// SUV/MUV TIER
|
||||
{
|
||||
id: 4,
|
||||
name: "Toyota Fortuner 4x4",
|
||||
type: "SUV",
|
||||
seats: "6+1",
|
||||
luggage: "4 Bags",
|
||||
image: "fortuner",
|
||||
baseFarePerDay: 6500,
|
||||
driverAllowancePerDay: 500,
|
||||
extraKmRate: 25,
|
||||
includedKmPerDay: 300,
|
||||
badge: "Advanture King",
|
||||
features: ["Off-road capable", "Powerful AC", "Spacious", "Rugged Built"]
|
||||
},
|
||||
{
|
||||
id: 5,
|
||||
name: "Innova Crysta",
|
||||
type: "MUV",
|
||||
seats: "7+1",
|
||||
luggage: "3 Bags",
|
||||
image: "innova_crysta",
|
||||
baseFarePerDay: 4500,
|
||||
driverAllowancePerDay: 400,
|
||||
extraKmRate: 18,
|
||||
includedKmPerDay: 300,
|
||||
badge: "Best Seller",
|
||||
features: ["Clean Interiors", "Captain Seats", "Smooth Ride", "Carrier Available"]
|
||||
},
|
||||
{
|
||||
id: 6,
|
||||
name: "Toyota Innova",
|
||||
type: "MUV",
|
||||
seats: "7+1",
|
||||
luggage: "2 Bags",
|
||||
image: "innova",
|
||||
baseFarePerDay: 3800,
|
||||
driverAllowancePerDay: 400,
|
||||
extraKmRate: 16,
|
||||
includedKmPerDay: 300,
|
||||
badge: "Value MUV",
|
||||
features: ["Comfortable", "Spacious", "Economical for Family"]
|
||||
},
|
||||
// SEDAN TIER
|
||||
{
|
||||
id: 7,
|
||||
name: "Toyota Camry",
|
||||
type: "SEDAN",
|
||||
seats: "4+1",
|
||||
luggage: "2 Bags",
|
||||
image: "camry",
|
||||
baseFarePerDay: 4200,
|
||||
driverAllowancePerDay: 400,
|
||||
extraKmRate: 18,
|
||||
includedKmPerDay: 300,
|
||||
badge: "Corporate Elite",
|
||||
features: ["Hybrid Tech", "Quiet Ride", "Executive Space"]
|
||||
},
|
||||
{
|
||||
id: 8,
|
||||
name: "Toyota Etios",
|
||||
type: "SEDAN",
|
||||
seats: "4+1",
|
||||
luggage: "2 Bags",
|
||||
image: "etios",
|
||||
baseFarePerDay: 2800,
|
||||
driverAllowancePerDay: 400,
|
||||
extraKmRate: 13,
|
||||
includedKmPerDay: 300,
|
||||
badge: "Economic",
|
||||
features: ["Spacious Boot", "Good Legroom", "Standard AC"]
|
||||
},
|
||||
{
|
||||
id: 9,
|
||||
name: "Swift Dzire",
|
||||
type: "SEDAN",
|
||||
seats: "4+1",
|
||||
luggage: "2 Bags",
|
||||
image: "dzire",
|
||||
baseFarePerDay: 2600,
|
||||
driverAllowancePerDay: 400,
|
||||
extraKmRate: 13,
|
||||
includedKmPerDay: 300,
|
||||
badge: "Economic Choice",
|
||||
features: ["Popular", "Quick Maneuver", "Efficient AC"]
|
||||
},
|
||||
// GROUP TRAVEL
|
||||
{
|
||||
id: 11,
|
||||
name: "Audi Q7 Luxury SUV",
|
||||
type: "SUV",
|
||||
seats: "6+1",
|
||||
luggage: "4 Bags",
|
||||
image: "audi_q7",
|
||||
baseFarePerDay: 15000,
|
||||
driverAllowancePerDay: 700,
|
||||
extraKmRate: 55,
|
||||
includedKmPerDay: 250,
|
||||
badge: "Luxury SUV",
|
||||
features: ["Quattro 4x4", "Panoramic Sunroof", "Premium Audio", "Heated Seats"]
|
||||
},
|
||||
{
|
||||
id: 12,
|
||||
name: "BMW 5 Series",
|
||||
type: "LUXURY",
|
||||
seats: "4+1",
|
||||
luggage: "2 Bags",
|
||||
image: "bmw_5_series",
|
||||
baseFarePerDay: 14000,
|
||||
driverAllowancePerDay: 600,
|
||||
extraKmRate: 50,
|
||||
includedKmPerDay: 250,
|
||||
badge: "Business Class",
|
||||
features: ["Dynamic Drive", "Luxury Interiors", "Silent Hybrid", "Touchscreen Control"]
|
||||
},
|
||||
{
|
||||
id: 13,
|
||||
name: "Mercedes E-Class",
|
||||
type: "LUXURY",
|
||||
seats: "4+1",
|
||||
luggage: "2 Bags",
|
||||
image: "mercedes_e_class",
|
||||
baseFarePerDay: 14500,
|
||||
driverAllowancePerDay: 600,
|
||||
extraKmRate: 52,
|
||||
includedKmPerDay: 250,
|
||||
badge: "Timeless Elegance",
|
||||
features: ["Soft Close Doors", "Ambient Light", "Professional Driver", "WIFI Enabled"]
|
||||
},
|
||||
{
|
||||
id: 14,
|
||||
name: "Toyota Corolla Altis",
|
||||
type: "SEDAN",
|
||||
seats: "4+1",
|
||||
luggage: "2 Bags",
|
||||
image: "corolla_altis",
|
||||
baseFarePerDay: 3800,
|
||||
driverAllowancePerDay: 400,
|
||||
extraKmRate: 16,
|
||||
includedKmPerDay: 300,
|
||||
badge: "Executive Sedan",
|
||||
features: ["Durable", "Comfortable Suspension", "Clean Air Filter"]
|
||||
}
|
||||
];
|
||||
|
||||
// Lead Capture
|
||||
app.post("/api/leads", async (req, res) => {
|
||||
try {
|
||||
const { name, mobile_number, tripType, pickupLocation, dropLocation } = req.body;
|
||||
if (!name || !mobile_number) return res.status(400).json({ detail: "Name and Mobile are required" });
|
||||
|
||||
await pool.execute(
|
||||
`INSERT INTO Leads (name, mobile_number, trip_type, pickup_location, drop_location) VALUES (?, ?, ?, ?, ?)`,
|
||||
[name, mobile_number, tripType || null, pickupLocation || null, dropLocation || null]
|
||||
);
|
||||
res.status(201).json({ status: 1, message: "Lead captured" });
|
||||
} catch (error) {
|
||||
console.error("Lead Error:", error);
|
||||
res.status(500).json({ detail: "Failed to capture lead" });
|
||||
}
|
||||
});
|
||||
|
||||
// Centralized Pricing Calculator
|
||||
// --- DYNAMIC PRICING ENGINE (DB DRIVEN) ---
|
||||
app.post("/api/pricing/calculate", async (req, res) => {
|
||||
try {
|
||||
const { tripType, pickupDate, returnDate, packageType } = req.body;
|
||||
|
|
@ -331,7 +124,12 @@ app.post("/api/pricing/calculate", async (req, res) => {
|
|||
const diffTime = Math.abs(end - start);
|
||||
const durationDays = Math.ceil(diffTime / (1000 * 60 * 60 * 24)) || 1;
|
||||
|
||||
const results = VEHICLES.map(vehicle => {
|
||||
// Fetch ACTIVE vehicles from DB
|
||||
const [rows] = await pool.execute("SELECT * FROM Vehicles WHERE isAvailable = TRUE");
|
||||
|
||||
const results = rows.map(vehicle => {
|
||||
const features = typeof vehicle.features === 'string' ? JSON.parse(vehicle.features) : vehicle.features;
|
||||
|
||||
let totalBaseFare = vehicle.baseFarePerDay * durationDays;
|
||||
let totalDriverAllowance = vehicle.driverAllowancePerDay * durationDays;
|
||||
|
||||
|
|
@ -343,11 +141,11 @@ app.post("/api/pricing/calculate", async (req, res) => {
|
|||
totalDriverAllowance = 0;
|
||||
}
|
||||
|
||||
// Special Case: Airport Transfers (Flat Rates)
|
||||
// Special Case: Airport Transfers
|
||||
if (tripType === 'airport') {
|
||||
if (vehicle.type === 'SEDAN') totalBaseFare = 1200;
|
||||
else if (vehicle.type === 'SUV') totalBaseFare = 2200;
|
||||
else totalBaseFare = 1800; // MUV/Other
|
||||
else totalBaseFare = 1800;
|
||||
totalDriverAllowance = 0;
|
||||
}
|
||||
|
||||
|
|
@ -356,18 +154,19 @@ app.post("/api/pricing/calculate", async (req, res) => {
|
|||
|
||||
return {
|
||||
...vehicle,
|
||||
features,
|
||||
calc: {
|
||||
days: durationDays,
|
||||
baseFare: totalBaseFare,
|
||||
driverAllowance: totalDriverAllowance,
|
||||
totalKmIncluded: vehicle.includedKmPerDay * (tripType === 'airport' ? 0.2 : durationDays), // Approx for airport
|
||||
totalKmIncluded: vehicle.includedKmPerDay * (tripType === 'airport' ? 0.2 : durationDays),
|
||||
grandTotal,
|
||||
advancePayment,
|
||||
math: tripType === 'airport' || tripType === 'local'
|
||||
? `Fixed ${tripType.charAt(0).toUpperCase() + tripType.slice(1)} Rate`
|
||||
: `(₹${vehicle.baseFarePerDay} x ${durationDays} Days) + ₹${totalDriverAllowance} Driver`
|
||||
},
|
||||
inclusions: ["AC", "Professional Driver", tripType === 'airport' ? "Airport Toll Included" : "First " + (vehicle.includedKmPerDay * durationDays) + " Kms"],
|
||||
inclusions: ["AC", "Professional Driver", tripType === 'airport' ? "Airport Toll Included" : "First " + (vehicle.includedKmPerDay * (tripType === 'airport' ? 1 : durationDays)) + " Kms"],
|
||||
exclusions: ["Parking Charges", "Extra Waiting Time", "Inter-state Permits"]
|
||||
};
|
||||
});
|
||||
|
|
@ -379,6 +178,86 @@ app.post("/api/pricing/calculate", async (req, res) => {
|
|||
}
|
||||
});
|
||||
|
||||
// --- ADMIN API ENDPOINTS ---
|
||||
|
||||
// Secure Admin Login
|
||||
app.post("/api/admin/login", async (req, res) => {
|
||||
const { adminId, password } = req.body;
|
||||
|
||||
// Environment-based credentials for security
|
||||
const VALID_ADMIN_ID = process.env.ADMIN_ID || "admin";
|
||||
const VALID_ADMIN_PASS = process.env.ADMIN_PASSWORD || "admin123";
|
||||
|
||||
if (adminId === VALID_ADMIN_ID && password === VALID_ADMIN_PASS) {
|
||||
res.json({ success: true, token: "secure_session_token_xyz_987" });
|
||||
} else {
|
||||
res.status(401).json({ detail: "Invalid Admin ID or Password" });
|
||||
}
|
||||
});
|
||||
|
||||
// Get Full Fleet for Management
|
||||
app.get("/api/admin/vehicles", async (req, res) => {
|
||||
try {
|
||||
const [rows] = await pool.execute("SELECT * FROM Vehicles ORDER BY id DESC");
|
||||
const cars = rows.map(v => ({
|
||||
...v,
|
||||
features: typeof v.features === 'string' ? JSON.parse(v.features) : v.features
|
||||
}));
|
||||
res.json(cars);
|
||||
} catch (err) {
|
||||
res.status(500).json({ detail: "Fail to fetch fleet" });
|
||||
}
|
||||
});
|
||||
|
||||
// Update Availability or Pricing
|
||||
app.patch("/api/admin/vehicles/:id", async (req, res) => {
|
||||
try {
|
||||
const { id } = req.params;
|
||||
const { baseFarePerDay, isAvailable } = req.body;
|
||||
|
||||
if (baseFarePerDay !== undefined) {
|
||||
await pool.execute("UPDATE Vehicles SET baseFarePerDay = ? WHERE id = ?", [baseFarePerDay, id]);
|
||||
}
|
||||
if (isAvailable !== undefined) {
|
||||
await pool.execute("UPDATE Vehicles SET isAvailable = ? WHERE id = ?", [isAvailable, id]);
|
||||
}
|
||||
|
||||
res.json({ success: true, message: "Vehicle updated successfully" });
|
||||
} catch (err) {
|
||||
res.status(500).json({ detail: "Failed to update vehicle" });
|
||||
}
|
||||
});
|
||||
|
||||
// Add New Cab
|
||||
app.post("/api/admin/vehicles", async (req, res) => {
|
||||
try {
|
||||
const v = req.body;
|
||||
const features = JSON.stringify(v.features || []);
|
||||
|
||||
await pool.execute(
|
||||
`INSERT INTO Vehicles (name, type, seats, luggage, image, baseFarePerDay, driverAllowancePerDay, extraKmRate, includedKmPerDay, badge, features)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
||||
[v.name, v.type, v.seats, v.luggage, v.image, v.baseFarePerDay, v.driverAllowancePerDay, v.extraKmRate, v.includedKmPerDay, v.badge, features]
|
||||
);
|
||||
|
||||
res.status(201).json({ success: true, message: "New vehicle added" });
|
||||
} catch (err) {
|
||||
res.status(500).json({ detail: "Failed to add new vehicle" });
|
||||
}
|
||||
});
|
||||
|
||||
// Delete Cab
|
||||
app.delete("/api/admin/vehicles/:id", async (req, res) => {
|
||||
try {
|
||||
const { id } = req.params;
|
||||
await pool.execute("DELETE FROM Vehicles WHERE id = ?", [id]);
|
||||
res.json({ success: true, message: "Vehicle removed" });
|
||||
} catch (err) {
|
||||
res.status(500).json({ detail: "Failed to delete vehicle" });
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
const PORT = 8000;
|
||||
|
||||
app.listen(PORT, () => {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue