v2.0.0: Complete rewrite — React + Express + PostgreSQL + Docker
BREAKING: Full rewrite from static HTML to production-grade stack. Features: - React 18 + Vite + Tailwind CSS (mobile-first) - Express + Prisma + PostgreSQL backend - JWT authentication with role-based access - Weekly Mon-Sun timesheets with auto-save - Multiple homeowner entries per day - Submit → Approve/Reject workflow - Server-side PDF generation - SMTP email integration - Admin panel with reporting & filters - Dark mode (system-aware) - Docker Compose one-command deploy - Non-root containers, Helmet, bcrypt, Zod validation
This commit is contained in:
@@ -0,0 +1,104 @@
|
||||
const express = require('express');
|
||||
const cors = require('cors');
|
||||
const helmet = require('helmet');
|
||||
const { PrismaClient } = require('@prisma/client');
|
||||
|
||||
const authRoutes = require('./routes/auth');
|
||||
const entriesRoutes = require('./routes/entries');
|
||||
const timesheetsRoutes = require('./routes/timesheets');
|
||||
const adminRoutes = require('./routes/admin');
|
||||
const homeownersRoutes = require('./routes/homeowners');
|
||||
|
||||
const app = express();
|
||||
const prisma = new PrismaClient();
|
||||
const PORT = process.env.PORT || 3001;
|
||||
|
||||
// Trust proxy (behind nginx)
|
||||
app.set('trust proxy', 1);
|
||||
|
||||
// Security headers
|
||||
app.use(helmet());
|
||||
|
||||
// CORS
|
||||
const allowedOrigins = process.env.CORS_ORIGINS
|
||||
? process.env.CORS_ORIGINS.split(',').map((o) => o.trim())
|
||||
: ['http://localhost:5173', 'http://localhost:3000'];
|
||||
|
||||
app.use(
|
||||
cors({
|
||||
origin(origin, callback) {
|
||||
// Allow requests with no origin (mobile apps, curl, etc.)
|
||||
if (!origin || allowedOrigins.includes(origin)) {
|
||||
callback(null, true);
|
||||
} else {
|
||||
callback(new Error('Not allowed by CORS'));
|
||||
}
|
||||
},
|
||||
credentials: true,
|
||||
})
|
||||
);
|
||||
|
||||
// Body parsing
|
||||
app.use(express.json({ limit: '1mb' }));
|
||||
|
||||
// Handle malformed JSON errors
|
||||
app.use((err, req, res, next) => {
|
||||
if (err.type === 'entity.parse.failed') {
|
||||
return res.status(400).json({ error: 'Invalid JSON body' });
|
||||
}
|
||||
next(err);
|
||||
});
|
||||
|
||||
// Attach prisma to request
|
||||
app.use((req, _res, next) => {
|
||||
req.prisma = prisma;
|
||||
next();
|
||||
});
|
||||
|
||||
// Health check
|
||||
app.get('/api/health', async (_req, res) => {
|
||||
try {
|
||||
await prisma.$queryRaw`SELECT 1`;
|
||||
res.json({ status: 'ok', timestamp: new Date().toISOString() });
|
||||
} catch (err) {
|
||||
res.status(503).json({ status: 'error', message: 'Database unavailable' });
|
||||
}
|
||||
});
|
||||
|
||||
// Routes
|
||||
app.use('/api/auth', authRoutes);
|
||||
app.use('/api/entries', entriesRoutes);
|
||||
app.use('/api/timesheets', timesheetsRoutes);
|
||||
app.use('/api/homeowners', homeownersRoutes);
|
||||
app.use('/api/admin', adminRoutes);
|
||||
|
||||
// 404 handler
|
||||
app.use((_req, res) => {
|
||||
res.status(404).json({ error: 'Not found' });
|
||||
});
|
||||
|
||||
// Global error handler
|
||||
app.use((err, _req, res, _next) => {
|
||||
console.error('Unhandled error:', err);
|
||||
if (err.message === 'Not allowed by CORS') {
|
||||
return res.status(403).json({ error: 'CORS policy violation' });
|
||||
}
|
||||
res.status(500).json({ error: 'Internal server error' });
|
||||
});
|
||||
|
||||
// Graceful shutdown
|
||||
async function shutdown(signal) {
|
||||
console.log(`\n${signal} received. Shutting down gracefully...`);
|
||||
await prisma.$disconnect();
|
||||
process.exit(0);
|
||||
}
|
||||
process.on('SIGTERM', () => shutdown('SIGTERM'));
|
||||
process.on('SIGINT', () => shutdown('SIGINT'));
|
||||
|
||||
// Start server
|
||||
app.listen(PORT, '0.0.0.0', () => {
|
||||
console.log(`🚀 Coastal Timesheet API running on port ${PORT}`);
|
||||
console.log(`📋 Health check: http://localhost:${PORT}/api/health`);
|
||||
});
|
||||
|
||||
module.exports = app;
|
||||
Reference in New Issue
Block a user