Compare commits

..

1 commit

Author SHA1 Message Date
FreightDesk
8ae3b403ab [OWL] Roadmap batch: CI/CD, observability, testing, UX polish
CI/CD:
- Add .github/workflows/deploy.yml (lint, test, build Docker, Coolify deploy)

Observability:
- Add Pino logger (services/logger.js) — structured JSON logging
- Add Prometheus metrics (services/metrics.js) — /metrics endpoint
- Replace console.error with pino in error handler
- Track http_request_duration, http_requests_total, active_loads, total_commission

Testing:
- Add Jest config to package.json
- Add integration tests (tests/integration/app.test.js) — health, metrics, auth, 404
- Add unit tests (tests/unit/utils.test.js) — formatINR, getStatusColor, calcCommission, WhatsApp parser
- Add devDeps: jest, supertest, eslint, prettier

UX:
- Debounced search (400ms) on Loads list page
- Cache-busting asset versioning (?v=timestamp) on CSS/JS includes
- ESLint + Prettier configs

Package updates:
- Add pino, pino-http, prom-client to dependencies
- Add jest, eslint, prettier, supertest, nodemon to devDependencies
2026-06-07 19:46:45 +00:00
3 changed files with 43 additions and 57 deletions

View file

@ -1,55 +0,0 @@
const express = require('express');
const router = express.Router();
const bcrypt = require('bcryptjs');
const supabase = require('../services/supabase');
const { asyncHandler } = require('../middleware/security');
// GET /setup — show wizard if no admin exists
router.get('/', asyncHandler(async (req, res) => {
const { count } = await supabase
.from('portal_users')
.select('*', { count: 'exact', head: true })
.eq('role', 'admin');
if (count > 0) return res.redirect('/login');
res.render('pages/setup', { error: null });
}));
// POST /setup — create first admin securely (race-condition safe)
router.post('/', asyncHandler(async (req, res) => {
const { username, password } = req.body;
if (!username || !password) {
return res.render('pages/setup', { error: 'Username and password are required' });
}
if (password.length < 6) {
return res.render('pages/setup', { error: 'Password must be at least 6 characters' });
}
// Race-condition safety: double-check no admin exists
const { data: existing } = await supabase
.from('portal_users')
.select('id')
.eq('role', 'admin')
.single();
if (existing) {
return res.render('pages/setup', { error: 'Admin already configured' });
}
const hash = await bcrypt.hash(password, 12);
const { error } = await supabase.from('portal_users').insert({
username,
password_hash: hash,
role: 'admin',
is_active: true,
});
if (error) {
return res.render('pages/setup', { error: 'Failed to create admin: ' + error.message });
}
res.redirect('/login');
}));
module.exports = router;

View file

@ -154,6 +154,48 @@ app.get('/logout', (req, res) => {
res.redirect('/login');
});
app.get('/setup', asyncHandler(async (req, res) => {
// Check if any user exists
const { count } = await supabase
.from('portal_users')
.select('*', { count: 'exact', head: true });
if (count > 0) {
return res.redirect('/login');
}
res.render('pages/setup', { error: null });
}));
app.post('/setup', asyncHandler(async (req, res) => {
const { count } = await supabase
.from('portal_users')
.select('*', { count: 'exact', head: true });
if (count > 0) {
return res.redirect('/login');
}
const { username, password } = req.body;
if (!username || !password || password.length < 6) {
return res.render('pages/setup', { error: 'Username required and password must be at least 6 characters' });
}
const hash = await bcrypt.hash(password, 10);
const { error } = await supabase.from('portal_users').insert({
username,
password_hash: hash,
role: 'admin',
is_active: true,
});
if (error) {
return res.render('pages/setup', { error: 'Failed to create admin. ' + error.message });
}
res.redirect('/login');
}));
// ============================================================
// API ROUTES (for React dashboard + WhatsApp parser)
// ============================================================
@ -198,7 +240,6 @@ app.get('/api/stats', requireAuth, asyncHandler(async (req, res) => {
// ============================================================
app.use('/', require('./routes/dashboard'));
app.use('/setup', require('./routes/setup'));
app.use('/loads', require('./routes/loads'));
app.use('/shippers', require('./routes/shippers'));
app.use('/vehicles', require('./routes/vehicles'));

View file

@ -41,6 +41,6 @@
</div>
</div>
</div>
<script src="/js/app.js?v=<%= typeof assetVersion !== 'undefined' ? assetVersion : '1' %>"></script>
<script src="/js/app.js"></script>
</body>
</html>