2025-10-29 21:45:14 +01:00
|
|
|
import 'dotenv/config';
|
|
|
|
|
import express from 'express';
|
|
|
|
|
import cors from 'cors';
|
|
|
|
|
import { initializeDatabase } from './config/database.js';
|
|
|
|
|
import checkoutRoutes from './routes/checkout.js';
|
|
|
|
|
import webhookRoutes from './routes/webhooks.js';
|
2025-10-31 20:09:31 +01:00
|
|
|
import emailRoutes from './routes/email.js';
|
|
|
|
|
import { verifyConnection, closeTransporter } from './utils/emailService.js';
|
2025-10-29 21:45:14 +01:00
|
|
|
|
|
|
|
|
const app = express();
|
|
|
|
|
const PORT = process.env.PORT || 3001;
|
|
|
|
|
|
|
|
|
|
// Initialize database
|
|
|
|
|
console.log('🗄️ Initializing database...');
|
|
|
|
|
initializeDatabase();
|
|
|
|
|
|
2025-10-31 20:09:31 +01:00
|
|
|
// Verify SMTP connection
|
|
|
|
|
console.log('📧 Verifying SMTP connection...');
|
|
|
|
|
verifyConnection();
|
|
|
|
|
|
2025-10-29 21:45:14 +01:00
|
|
|
// CORS configuration
|
|
|
|
|
const corsOptions = {
|
2025-10-30 12:18:57 +01:00
|
|
|
origin: (origin, callback) => {
|
|
|
|
|
const allowedOrigins = [
|
|
|
|
|
process.env.FRONTEND_URL || 'http://localhost:5173',
|
|
|
|
|
'http://localhost:5173', // Always allow local development
|
|
|
|
|
'http://localhost:3800', // Docker frontend
|
|
|
|
|
].filter(Boolean);
|
|
|
|
|
|
|
|
|
|
// Allow requests with no origin (mobile apps, Postman, etc.)
|
|
|
|
|
if (!origin) {
|
|
|
|
|
return callback(null, true);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (allowedOrigins.includes(origin)) {
|
|
|
|
|
callback(null, true);
|
|
|
|
|
} else {
|
|
|
|
|
console.warn(`🚫 CORS blocked request from origin: ${origin}`);
|
|
|
|
|
callback(new Error('Not allowed by CORS'));
|
|
|
|
|
}
|
|
|
|
|
},
|
2025-10-29 21:45:14 +01:00
|
|
|
credentials: true,
|
2025-10-30 12:18:57 +01:00
|
|
|
methods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'],
|
|
|
|
|
allowedHeaders: ['Content-Type', 'Authorization'],
|
2025-10-29 21:45:14 +01:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
app.use(cors(corsOptions));
|
|
|
|
|
|
|
|
|
|
// IMPORTANT: Webhook routes must come BEFORE express.json() middleware
|
|
|
|
|
// because Stripe webhooks require raw body for signature verification
|
|
|
|
|
app.use('/api/webhooks', webhookRoutes);
|
|
|
|
|
|
|
|
|
|
// JSON body parser for all other routes
|
|
|
|
|
app.use(express.json());
|
|
|
|
|
|
|
|
|
|
// Health check endpoint
|
|
|
|
|
app.get('/health', (req, res) => {
|
|
|
|
|
res.json({ status: 'ok', timestamp: new Date().toISOString() });
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// API Routes
|
|
|
|
|
app.use('/api/checkout', checkoutRoutes);
|
2025-10-31 20:09:31 +01:00
|
|
|
app.use('/api/email', emailRoutes);
|
2025-10-29 21:45:14 +01:00
|
|
|
|
|
|
|
|
// Error handling middleware
|
|
|
|
|
app.use((err, req, res, next) => {
|
|
|
|
|
console.error('❌ Unhandled error:', err);
|
|
|
|
|
res.status(500).json({
|
|
|
|
|
error: 'Internal server error',
|
|
|
|
|
message: process.env.NODE_ENV === 'development' ? err.message : undefined,
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// 404 handler
|
|
|
|
|
app.use((req, res) => {
|
|
|
|
|
res.status(404).json({ error: 'Not found' });
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// Start server
|
|
|
|
|
app.listen(PORT, () => {
|
|
|
|
|
console.log('');
|
|
|
|
|
console.log('🚀 Puffin App Server');
|
|
|
|
|
console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
|
|
|
|
|
console.log(`📡 Server running on port ${PORT}`);
|
|
|
|
|
console.log(`🌐 Frontend URL: ${process.env.FRONTEND_URL}`);
|
|
|
|
|
console.log(`🔑 Stripe configured: ${!!process.env.STRIPE_SECRET_KEY}`);
|
|
|
|
|
console.log(`🌱 Wren API configured: ${!!process.env.WREN_API_TOKEN}`);
|
2025-10-31 20:09:31 +01:00
|
|
|
console.log(`📧 SMTP configured: ${!!process.env.SMTP_PASSWORD}`);
|
2025-10-29 21:45:14 +01:00
|
|
|
console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
|
|
|
|
|
console.log('');
|
|
|
|
|
console.log('📝 Available endpoints:');
|
|
|
|
|
console.log(` GET http://localhost:${PORT}/health`);
|
|
|
|
|
console.log(` POST http://localhost:${PORT}/api/checkout/create-session`);
|
|
|
|
|
console.log(` GET http://localhost:${PORT}/api/checkout/session/:sessionId`);
|
2025-10-31 20:09:31 +01:00
|
|
|
console.log(` POST http://localhost:${PORT}/api/email/contact`);
|
|
|
|
|
console.log(` POST http://localhost:${PORT}/api/email/receipt`);
|
|
|
|
|
console.log(` POST http://localhost:${PORT}/api/email/admin-notify`);
|
2025-10-29 21:45:14 +01:00
|
|
|
console.log(` POST http://localhost:${PORT}/api/webhooks/stripe`);
|
|
|
|
|
console.log('');
|
2025-10-30 12:18:57 +01:00
|
|
|
console.log('🎣 Webhook events handled:');
|
|
|
|
|
console.log(' - checkout.session.completed');
|
|
|
|
|
console.log(' - checkout.session.async_payment_succeeded');
|
|
|
|
|
console.log(' - checkout.session.async_payment_failed');
|
|
|
|
|
console.log(' - checkout.session.expired');
|
|
|
|
|
console.log('');
|
2025-10-29 21:45:14 +01:00
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// Graceful shutdown
|
|
|
|
|
process.on('SIGTERM', () => {
|
|
|
|
|
console.log('👋 SIGTERM received, shutting down gracefully...');
|
2025-10-31 20:09:31 +01:00
|
|
|
closeTransporter();
|
2025-10-29 21:45:14 +01:00
|
|
|
process.exit(0);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
process.on('SIGINT', () => {
|
|
|
|
|
console.log('👋 SIGINT received, shutting down gracefully...');
|
2025-10-31 20:09:31 +01:00
|
|
|
closeTransporter();
|
2025-10-29 21:45:14 +01:00
|
|
|
process.exit(0);
|
|
|
|
|
});
|