mirror of
https://github.com/R0m1k3/podcastic.git
synced 2026-10-22 06:28:03 +02:00
- Remove separate backend/ and frontend/ Dockerfiles - Add root Dockerfile (3-stage: frontend build → backend build → runtime) - Frontend static files served by Express from /public - Simplify docker-compose to 3 services: mongodb, redis, app (port 3579) - Fix vite proxy not to strip /api prefix from paths - Default API URL to relative /api instead of hardcoded localhost Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
103 lines
2.7 KiB
TypeScript
103 lines
2.7 KiB
TypeScript
import express from 'express';
|
|
import cors from 'cors';
|
|
import 'dotenv/config';
|
|
import path from 'path';
|
|
import { fileURLToPath } from 'url';
|
|
import { connectDB } from './config/database';
|
|
import { initRedis, closeRedis } from './config/redis';
|
|
import { errorHandler } from './middleware/errorHandler';
|
|
import authRoutes from './routes/authRoutes';
|
|
import podcastRoutes from './routes/podcastRoutes';
|
|
import episodeRoutes from './routes/episodeRoutes';
|
|
|
|
const __filename = fileURLToPath(import.meta.url);
|
|
const __dirname = path.dirname(__filename);
|
|
|
|
const app = express();
|
|
const PORT = process.env.PORT || 5000;
|
|
|
|
// Middleware
|
|
app.use(cors());
|
|
app.use(express.json());
|
|
app.use(express.urlencoded({ limit: '10mb', extended: true }));
|
|
|
|
// Initialize databases on startup
|
|
let isInitialized = false;
|
|
|
|
const initialize = async () => {
|
|
if (isInitialized) return;
|
|
|
|
try {
|
|
await connectDB();
|
|
await initRedis();
|
|
isInitialized = true;
|
|
console.log('✓ All services initialized');
|
|
} catch (error) {
|
|
console.error('✗ Initialization failed:', error);
|
|
process.exit(1);
|
|
}
|
|
};
|
|
|
|
// Health check endpoint (no auth required)
|
|
app.get('/health', (req, res) => {
|
|
res.json({ status: 'ok', timestamp: new Date().toISOString() });
|
|
});
|
|
|
|
// API Routes
|
|
app.get('/api/health', (req, res) => {
|
|
res.json({ status: 'ok', message: 'API is running' });
|
|
});
|
|
|
|
app.use('/api/auth', authRoutes);
|
|
app.use('/api/podcasts', podcastRoutes);
|
|
app.use('/api/episodes', episodeRoutes);
|
|
|
|
// Serve frontend static files in production
|
|
const frontendDist = path.join(__dirname, '..', 'public');
|
|
app.use(express.static(frontendDist));
|
|
|
|
// SPA fallback — return index.html for any non-API route
|
|
app.get('*', (req, res) => {
|
|
const indexFile = path.join(frontendDist, 'index.html');
|
|
res.sendFile(indexFile, (err) => {
|
|
if (err) {
|
|
res.status(404).json({ success: false, message: 'Not found' });
|
|
}
|
|
});
|
|
});
|
|
|
|
// Error handler (must be last)
|
|
app.use(errorHandler);
|
|
|
|
// Graceful shutdown
|
|
const gracefulShutdown = async () => {
|
|
console.log('\nShutting down gracefully...');
|
|
await closeRedis();
|
|
process.exit(0);
|
|
};
|
|
|
|
process.on('SIGTERM', gracefulShutdown);
|
|
process.on('SIGINT', gracefulShutdown);
|
|
|
|
// Start server
|
|
const startServer = async () => {
|
|
try {
|
|
await initialize();
|
|
|
|
app.listen(PORT, () => {
|
|
console.log(`\n${'='.repeat(50)}`);
|
|
console.log(`🎙️ Podcastic Backend`);
|
|
console.log(`Server running on http://localhost:${PORT}`);
|
|
console.log(`Environment: ${process.env.NODE_ENV || 'development'}`);
|
|
console.log(`${'='.repeat(50)}\n`);
|
|
});
|
|
} catch (error) {
|
|
console.error('Failed to start server:', error);
|
|
process.exit(1);
|
|
}
|
|
};
|
|
|
|
startServer();
|
|
|
|
export default app;
|