const express = require('express'); const cors = require('cors'); const helmet = require('helmet'); const path = require('path'); const fs = require('fs'); const { initDatabase } = require('./db/init'); const app = express(); const PORT = process.env.PORT || 3000; const DB_PATH = process.env.DB_PATH || path.join(__dirname, '../data/breedr.db'); const UPLOAD_PATH = process.env.UPLOAD_PATH || path.join(__dirname, '../uploads'); const STATIC_PATH = process.env.STATIC_PATH || path.join(__dirname, '../static'); // Ensure directories exist const dataDir = path.dirname(DB_PATH); if (!fs.existsSync(dataDir)) { fs.mkdirSync(dataDir, { recursive: true }); } if (!fs.existsSync(UPLOAD_PATH)) { fs.mkdirSync(UPLOAD_PATH, { recursive: true }); } if (!fs.existsSync(STATIC_PATH)) { fs.mkdirSync(STATIC_PATH, { recursive: true }); } // Initialize database schema (creates tables if they don't exist) console.log('Initializing database...'); initDatabase(DB_PATH); console.log('āœ“ Database ready!\n'); // Middleware app.use(helmet({ contentSecurityPolicy: false, // Allow inline scripts for React })); app.use(cors()); app.use(express.json()); app.use(express.urlencoded({ extended: true })); // Static asset routes — registered BEFORE React catch-all so they are // resolved directly and never fall through to index.html app.use('/uploads', express.static(UPLOAD_PATH)); app.use('/static', express.static(STATIC_PATH)); // Explicit 404 for missing asset files so the catch-all never intercepts them app.use('/uploads', (req, res) => res.status(404).json({ error: 'Upload not found' })); app.use('/static', (req, res) => res.status(404).json({ error: 'Static asset not found' })); // API Routes app.use('/api/dogs', require('./routes/dogs')); app.use('/api/litters', require('./routes/litters')); app.use('/api/health', require('./routes/health')); app.use('/api/pedigree', require('./routes/pedigree')); app.use('/api/breeding', require('./routes/breeding')); // Health check endpoint app.get('/api/health', (req, res) => { res.json({ status: 'ok', timestamp: new Date().toISOString() }); }); // Serve React frontend in production // The catch-all is intentionally placed AFTER all asset/API routes above. // express.static(clientBuildPath) handles real build assets (JS/CSS chunks). // The scoped '*' only fires for HTML5 client-side routes (e.g. /dogs, /litters). if (process.env.NODE_ENV === 'production') { const clientBuildPath = path.join(__dirname, '../client/dist'); app.use(express.static(clientBuildPath)); // Only send index.html for non-asset, non-api paths app.get(/^(?!\/(api|static|uploads)\/).*$/, (req, res) => { res.sendFile(path.join(clientBuildPath, 'index.html')); }); } // Error handling middleware app.use((err, req, res, next) => { console.error('Error:', err); res.status(err.status || 500).json({ error: err.message || 'Internal server error', ...(process.env.NODE_ENV === 'development' && { stack: err.stack }) }); }); // Start server app.listen(PORT, '0.0.0.0', () => { console.log(`\nšŸ• BREEDR Server Running`); console.log(`==============================`); console.log(`Environment: ${process.env.NODE_ENV || 'development'}`); console.log(`Port: ${PORT}`); console.log(`Database: ${DB_PATH}`); console.log(`Uploads: ${UPLOAD_PATH}`); console.log(`Static: ${STATIC_PATH}`); console.log(`Access: http://localhost:${PORT}`); console.log(`==============================\n`); }); module.exports = app;