bharath/webapp/src/server.js
Vivek 394117dd74 BharathTrucks MVP - 6 sprints complete
- Govt-app styled freight marketplace
- Role-based auth (driver/shipper/broker/admin)
- Load board with bidding system
- Trip tracking with status flow
- In-app messaging
- Admin panel
- Mobile bottom nav + PWA
- Docker + Coolify ready
2026-05-31 06:21:13 +00:00

138 lines
5.5 KiB
JavaScript

require('dotenv').config();
const express = require('express');
const path = require('path');
const helmet = require('helmet');
const compression = require('compression');
const session = require('express-session');
const rateLimit = require('express-rate-limit');
const config = require('./config/env');
const app = express();
// Security
app.use(helmet({
contentSecurityPolicy: {
directives: {
defaultSrc: ["'self'"],
styleSrc: ["'self'", "'unsafe-inline'", "https://fonts.googleapis.com"],
fontSrc: ["'self'", "https://fonts.gstatic.com"],
imgSrc: ["'self'", "data:", "https:"],
scriptSrc: ["'self'", "'unsafe-inline'"],
},
},
}));
app.use(compression());
app.use(rateLimit({ windowMs: 60 * 1000, max: 100 }));
// Body parsing
app.use(express.json());
app.use(express.urlencoded({ extended: true }));
// Static files
app.use(express.static(path.join(__dirname, 'public')));
// View engine
app.set('view engine', 'ejs');
app.set('views', path.join(__dirname, 'views'));
// Session
app.use(session({
secret: config.session.secret,
resave: false,
saveUninitialized: false,
cookie: { secure: config.nodeEnv === 'production', maxAge: 24 * 60 * 60 * 1000 },
}));
// Make user available to all views
app.use((req, res, next) => {
res.locals.user = req.session.user || null;
res.locals.appName = 'भारत ट्रक्स';
res.locals.appNameEn = 'BharathTrucks';
next();
});
// Routes
const authRoutes = require('./routes/auth');
const loadRoutes = require('./routes/loads');
const tripRoutes = require('./routes/trips');
const adminRoutes = require('./routes/admin');
const messageRoutes = require('./routes/messages');
app.use('/', authRoutes);
app.use('/loadboard', loadRoutes);
app.use('/trips', tripRoutes);
app.use('/admin', adminRoutes);
app.use('/messages', messageRoutes);
const { requireAuth, requireDriver, requireShipper, requireBroker } = require('./middleware/auth');
const supabase = require('./services/supabase');
app.get('/health', (req, res) => res.json({ status: 'ok', ts: Date.now() }));
app.get('/', (req, res) => {
if (req.session && req.session.user) {
const { ROLES } = require('./config/constants');
if (req.session.user.role === ROLES.DRIVER) return res.redirect('/driver');
if (req.session.user.role === ROLES.SHIPPER) return res.redirect('/shipper');
if (req.session.user.role === ROLES.BROKER) return res.redirect('/broker');
}
res.render('pages/landing');
});
// Dashboards
app.get('/profile', requireAuth, async (req, res) => {
const { data: profile } = await supabase.from('app_users').select('*').eq('id', req.session.user.id).single();
res.render('pages/profile', { profile: profile || req.session.user, success: req.query.ok });
});
app.post('/profile', requireAuth, async (req, res) => {
const { name, phone, city, state } = req.body;
await supabase.from('app_users').update({ name: name.trim(), phone: phone || null, city: city || null, state: state || null }).eq('id', req.session.user.id);
req.session.user.name = name.trim();
res.redirect('/profile?ok=1');
});
app.get('/driver', requireAuth, requireDriver, async (req, res) => {
const userId = req.session.user.id;
const { data: bids } = await supabase.from('bids').select('status').eq('driver_id', userId);
const { data: trips } = await supabase.from('trips').select('*, load:load_id(origin_city, destination_city)').eq('driver_id', userId).order('created_at', { ascending: false });
const activeTrips = (trips || []).filter(t => !['delivered', 'cancelled'].includes(t.status));
const delivered = (trips || []).filter(t => t.status === 'delivered');
const earnings = delivered.reduce((s, t) => s + (parseFloat(t.amount) || 0), 0);
res.render('pages/driver-dashboard', {
stats: { totalTrips: (trips || []).length, activeBids: (bids || []).filter(b => b.status === 'pending').length, earnings },
activeTrips,
});
});
app.get('/shipper', requireAuth, requireShipper, async (req, res) => {
const userId = req.session.user.id;
const { data: loads } = await supabase.from('loads').select('*').eq('posted_by', userId).order('created_at', { ascending: false }).limit(10);
const { data: trips } = await supabase.from('trips').select('status').eq('shipper_id', userId);
const allLoads = loads || [];
res.render('pages/shipper-dashboard', {
stats: { totalLoads: allLoads.length, openLoads: allLoads.filter(l => l.status === 'open').length, activeTrips: (trips || []).filter(t => !['delivered', 'cancelled'].includes(t.status)).length },
recentLoads: allLoads.slice(0, 5),
});
});
app.get('/broker', requireAuth, requireBroker, async (req, res) => {
const userId = req.session.user.id;
const { data: loads } = await supabase.from('loads').select('*').eq('posted_by', userId).order('created_at', { ascending: false }).limit(10);
const { data: trips } = await supabase.from('trips').select('status').eq('shipper_id', userId);
const allLoads = loads || [];
res.render('pages/broker-dashboard', {
stats: { totalLoads: allLoads.length, bookedLoads: allLoads.filter(l => l.status === 'booked').length, activeTrips: (trips || []).filter(t => !['delivered', 'cancelled'].includes(t.status)).length },
recentLoads: allLoads.slice(0, 5),
});
});
// 404
app.use((req, res) => res.status(404).render('pages/404'));
// Error handler
app.use((err, req, res, next) => {
console.error(err.stack);
res.status(500).render('pages/500');
});
app.listen(config.port, '0.0.0.0', () => {
console.log(`BharathTrucks running at http://localhost:${config.port}`);
});