Download ops/backend-dm/controllers/shipmentController.js from MouleeswaranM/FairRelay: direct link, hf CLI and curl.
- Browser
- Download file 11.1 kB
-
https://huggingface.co/MouleeswaranM/FairRelay/resolve/refs%2Fpr%2F21/ops/backend-dm/controllers/shipmentController.js
- Command line
-
hf download hf://MouleeswaranM/FairRelay@refs/pr/21/ops/backend-dm/controllers/shipmentController.js
-
curl -L -o shipmentController.js https://huggingface.co/MouleeswaranM/FairRelay/resolve/refs%2Fpr%2F21/ops/backend-dm/controllers/shipmentController.js
11.1 kB
| const prisma = require('../config/database'); | |
| const axios = require('axios'); | |
| const Joi = require('joi'); | |
| const { AI_SERVICE_URL } = process.env; | |
| /** | |
| * POST /api/shipments/create - Create shipment (calls XGBoost) | |
| */ | |
| const createShipment = async (req, res) => { | |
| try { | |
| // Validation | |
| const schema = Joi.object({ | |
| pickupLat: Joi.number().required(), | |
| pickupLng: Joi.number().required(), | |
| pickupLocation: Joi.string().max(200).required(), | |
| dropLat: Joi.number().required(), | |
| dropLng: Joi.number().required(), | |
| dropLocation: Joi.string().max(200).required(), | |
| cargoType: Joi.string().valid( | |
| 'Electronics', 'Industrial Machinery', 'Textiles', | |
| 'Automotive Parts', 'FMCG Products', 'Pharmaceuticals', | |
| 'Steel & Metal', 'Agricultural Products', 'Furniture' | |
| ).required(), | |
| cargoWeight: Joi.number().min(0.1).max(50).required(), | |
| specialInstructions: Joi.string().max(500).optional(), | |
| priority: Joi.string().valid('LOW', 'MEDIUM', 'HIGH').default('LOW'), | |
| }); | |
| const { error } = schema.validate(req.body); | |
| if (error) { | |
| return res.status(400).json({ | |
| success: false, | |
| message: error.details[0].message, | |
| }); | |
| } | |
| const { | |
| pickupLat, pickupLng, pickupLocation, | |
| dropLat, dropLng, dropLocation, | |
| cargoType, cargoWeight, specialInstructions, priority | |
| } = req.body; | |
| // Calculate distance (Haversine formula) | |
| const distanceKm = calculateDistance( | |
| { lat: pickupLat, lng: pickupLng }, | |
| { lat: dropLat, lng: dropLng } | |
| ); | |
| // Call XGBoost AI Pricing Service | |
| let aiPriceResponse; | |
| try { | |
| aiPriceResponse = await axios.post(`${AI_SERVICE_URL}/pricing/predict`, { | |
| distance_km: distanceKm, | |
| cargo_weight_tonnes: cargoWeight, | |
| cargo_type: cargoType, | |
| pickup_city: extractCity(pickupLocation), | |
| drop_city: extractCity(dropLocation), | |
| time_of_day: new Date().getHours() < 18 ? 'day' : 'night', | |
| day_of_week: ['sunday', 'monday', 'tuesday', 'wednesday', 'thursday', 'friday', 'saturday'][new Date().getDay()], | |
| fuel_price: 98.5, | |
| traffic_level: 'medium', | |
| urgency: priority.toLowerCase(), | |
| }, { timeout: 5000 }); | |
| } catch (aiError) { | |
| console.log('❌ AI service unavailable, using fallback pricing'); | |
| // Fallback pricing formula | |
| aiPriceResponse = { | |
| data: { | |
| predicted_price: Math.round((distanceKm * 18 + cargoWeight * 100) / 10) * 10, | |
| confidence: 0.75, | |
| price_breakdown: { | |
| base_rate: Math.round(distanceKm * 15), | |
| distance_premium: Math.round(distanceKm * 2), | |
| weight_premium: Math.round(cargoWeight * 80), | |
| } | |
| } | |
| }; | |
| } | |
| // Create shipment | |
| const shipment = await prisma.shipment.create({ | |
| data: { | |
| shipperId: req.user.id, | |
| pickupLocation, | |
| pickupLat, | |
| pickupLng, | |
| dropLocation, | |
| dropLat, | |
| dropLng, | |
| cargoType, | |
| cargoWeight, | |
| specialInstructions: specialInstructions || null, | |
| estimatedPrice: aiPriceResponse.data.predicted_price, | |
| status: 'PENDING', | |
| priority, | |
| isMarketplaceLoad: Math.random() > 0.7, // 30% chance | |
| }, | |
| include: { | |
| shipper: { | |
| select: { name: true, phone: true } | |
| } | |
| } | |
| }); | |
| res.status(201).json({ | |
| success: true, | |
| message: 'Shipment created successfully', | |
| data: { | |
| id: shipment.id, | |
| status: shipment.status, | |
| estimatedPrice: shipment.estimatedPrice, | |
| aiConfidence: aiPriceResponse.data.confidence, | |
| distanceKm, | |
| priceBreakdown: aiPriceResponse.data.price_breakdown, | |
| shipment, | |
| }, | |
| }); | |
| } catch (error) { | |
| console.error('Create shipment error:', error); | |
| res.status(500).json({ | |
| success: false, | |
| message: 'Failed to create shipment', | |
| }); | |
| } | |
| }; | |
| /** | |
| * GET /api/shipments/my-shipments - List shipper's shipments | |
| */ | |
| const getMyShipments = async (req, res) => { | |
| try { | |
| const shipments = await prisma.shipment.findMany({ | |
| where: { shipperId: req.user.id }, | |
| orderBy: { createdAt: 'desc' }, | |
| include: { | |
| dispatcher: { | |
| select: { name: true } | |
| }, | |
| delivery: { | |
| include: { | |
| driver: { | |
| select: { name: true, rating: true, phone: true } | |
| }, | |
| truck: { | |
| select: { licensePlate: true, model: true } | |
| } | |
| } | |
| } | |
| }, | |
| take: 20, | |
| }); | |
| res.status(200).json({ | |
| success: true, | |
| data: shipments, | |
| count: shipments.length, | |
| }); | |
| } catch (error) { | |
| console.error('Get shipments error:', error); | |
| res.status(500).json({ | |
| success: false, | |
| message: 'Failed to fetch shipments', | |
| }); | |
| } | |
| }; | |
| /** | |
| * GET /api/shipments/:id - Get single shipment | |
| */ | |
| const getShipment = async (req, res) => { | |
| try { | |
| const { id } = req.params; | |
| const shipment = await prisma.shipment.findUnique({ | |
| where: { id }, | |
| include: { | |
| shipper: { | |
| select: { name: true, phone: true } | |
| }, | |
| dispatcher: { | |
| select: { name: true } | |
| }, | |
| delivery: { | |
| include: { | |
| driver: { | |
| select: { name: true, rating: true, phone: true } | |
| }, | |
| truck: { | |
| select: { licensePlate: true, model: true } | |
| } | |
| } | |
| } | |
| }, | |
| }); | |
| if (!shipment) { | |
| return res.status(404).json({ | |
| success: false, | |
| message: 'Shipment not found', | |
| }); | |
| } | |
| // Only shipper or dispatcher can view | |
| if (shipment.shipperId !== req.user.id && req.user.role !== 'DISPATCHER') { | |
| return res.status(403).json({ | |
| success: false, | |
| message: 'Access denied', | |
| }); | |
| } | |
| res.status(200).json({ | |
| success: true, | |
| data: shipment, | |
| }); | |
| } catch (error) { | |
| console.error('Get shipment error:', error); | |
| res.status(500).json({ | |
| success: false, | |
| message: 'Failed to fetch shipment', | |
| }); | |
| } | |
| }; | |
| /** | |
| * PUT /api/shipments/:id/cancel - Cancel pending shipment | |
| */ | |
| const cancelShipment = async (req, res) => { | |
| try { | |
| const { id } = req.params; | |
| const shipment = await prisma.shipment.findUnique({ | |
| where: { id }, | |
| }); | |
| if (!shipment) { | |
| return res.status(404).json({ | |
| success: false, | |
| message: 'Shipment not found', | |
| }); | |
| } | |
| // Only shipper can cancel their own shipment | |
| if (shipment.shipperId !== req.user.id) { | |
| return res.status(403).json({ | |
| success: false, | |
| message: 'Access denied', | |
| }); | |
| } | |
| // Only cancel PENDING shipments | |
| if (!['PENDING', 'AWAITING_DISPATCHER'].includes(shipment.status)) { | |
| return res.status(400).json({ | |
| success: false, | |
| message: 'Only pending shipments can be cancelled', | |
| }); | |
| } | |
| await prisma.shipment.update({ | |
| where: { id }, | |
| data: { status: 'CANCELLED' }, | |
| }); | |
| res.status(200).json({ | |
| success: true, | |
| message: 'Shipment cancelled successfully', | |
| }); | |
| } catch (error) { | |
| console.error('Cancel shipment error:', error); | |
| res.status(500).json({ | |
| success: false, | |
| message: 'Failed to cancel shipment', | |
| }); | |
| } | |
| }; | |
| /** | |
| * GET /api/shipments/pending - Get all pending shipments for drivers | |
| */ | |
| const getPendingShipments = async (req, res) => { | |
| try { | |
| const shipments = await prisma.shipment.findMany({ | |
| where: { | |
| status: 'PENDING', | |
| }, | |
| orderBy: { createdAt: 'desc' }, | |
| include: { | |
| shipper: { | |
| select: { name: true, phone: true } | |
| } | |
| }, | |
| take: 20, | |
| }); | |
| res.status(200).json({ | |
| success: true, | |
| data: shipments, | |
| count: shipments.length, | |
| }); | |
| } catch (error) { | |
| console.error('Get pending shipments error:', error); | |
| res.status(500).json({ | |
| success: false, | |
| message: 'Failed to fetch pending shipments', | |
| }); | |
| } | |
| }; | |
| /** | |
| * POST /api/shipments/:id/accept - Driver accepts a shipment | |
| */ | |
| const acceptShipment = async (req, res) => { | |
| try { | |
| const { id } = req.params; | |
| const driverId = req.user.id; | |
| const shipment = await prisma.shipment.findUnique({ | |
| where: { id }, | |
| }); | |
| if (!shipment) { | |
| return res.status(404).json({ | |
| success: false, | |
| message: 'Shipment not found', | |
| }); | |
| } | |
| if (shipment.status !== 'PENDING') { | |
| return res.status(400).json({ | |
| success: false, | |
| message: 'Shipment already assigned', | |
| }); | |
| } | |
| // Get driver's truck | |
| const truck = await prisma.truck.findFirst({ | |
| where: { driverId }, | |
| }); | |
| if (!truck) { | |
| return res.status(400).json({ | |
| success: false, | |
| message: 'No truck assigned to driver', | |
| }); | |
| } | |
| // Create delivery and update shipment in transaction | |
| const result = await prisma.$transaction(async (tx) => { | |
| // Update shipment status | |
| const updatedShipment = await tx.shipment.update({ | |
| where: { id }, | |
| data: { status: 'ASSIGNED' }, | |
| }); | |
| // Create delivery record | |
| const delivery = await tx.delivery.create({ | |
| data: { | |
| driverId, | |
| truckId: truck.id, | |
| shipmentId: id, | |
| pickupLocation: shipment.pickupLocation, | |
| pickupLat: shipment.pickupLat, | |
| pickupLng: shipment.pickupLng, | |
| dropLocation: shipment.dropLocation, | |
| dropLat: shipment.dropLat, | |
| dropLng: shipment.dropLng, | |
| cargoType: shipment.cargoType, | |
| cargoWeight: shipment.cargoWeight, | |
| status: 'ALLOCATED', | |
| estimatedPrice: shipment.estimatedPrice || 0, | |
| }, | |
| }); | |
| return { shipment: updatedShipment, delivery }; | |
| }); | |
| res.status(200).json({ | |
| success: true, | |
| message: 'Shipment accepted! Check your deliveries.', | |
| data: result, | |
| }); | |
| } catch (error) { | |
| console.error('Accept shipment error:', error); | |
| res.status(500).json({ | |
| success: false, | |
| message: 'Failed to accept shipment', | |
| }); | |
| } | |
| }; | |
| // Helper functions | |
| const calculateDistance = (point1, point2) => { | |
| const R = 6371; // Earth's radius in km | |
| const dLat = (point2.lat - point1.lat) * Math.PI / 180; | |
| const dLng = (point2.lng - point1.lng) * Math.PI / 180; | |
| const a = | |
| Math.sin(dLat / 2) * Math.sin(dLat / 2) + | |
| Math.cos(point1.lat * Math.PI / 180) * Math.cos(point2.lat * Math.PI / 180) * | |
| Math.sin(dLng / 2) * Math.sin(dLng / 2); | |
| const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a)); | |
| return Math.round(R * c); | |
| }; | |
| const extractCity = (location) => { | |
| const cities = ['Mumbai', 'Delhi', 'Bangalore', 'Pune', 'Hyderabad', 'Chennai']; | |
| const city = cities.find(city => location.includes(city)); | |
| return city || 'Other'; | |
| }; | |
| module.exports = { | |
| createShipment, | |
| getMyShipments, | |
| getShipment, | |
| cancelShipment, | |
| getPendingShipments, | |
| acceptShipment, | |
| }; |