const express = require('express'); const cors = require('cors'); const mongoose = require('mongoose'); const dotenv = require('dotenv'); const rateLimit = require('express-rate-limit'); // Import routes const { router: authRoutes } = require('./routes/auth'); const sourceTextRoutes = require('./routes/sourceTexts'); const submissionRoutes = require('./routes/submissions'); const searchRoutes = require('./routes/search'); const subtitleRoutes = require('./routes/subtitles'); const subtitleSubmissionRoutes = require('./routes/subtitleSubmissions'); const weeklyPracticeFilesRoutes = require('./routes/weeklyPracticeFiles'); const dictionaryRoutes = require('./routes/dictionary'); const icibaRoutes = require('./routes/iciba'); const oneRoutes = require('./routes/one'); const mtRoutes = require('./routes/mt'); const slidesRoutes = require('./routes/slides'); const messagesRoutes = require('./routes/messages'); const refinityRoutes = require('./routes/refinity'); const tutorialRefinityRoutes = require('./routes/tutorial-refinity'); const linksRoutes = require('./routes/links'); const docsRoutes = require('./routes/docs'); const adminWeeksRoutes = require('./routes/admin-weeks'); dotenv.config(); // Global error handlers to prevent crashes process.on('uncaughtException', (error) => { console.error('Uncaught Exception:', error); // Don't exit immediately, try to log and continue console.error('Stack trace:', error.stack); }); process.on('unhandledRejection', (reason, promise) => { console.error('Unhandled Rejection at:', promise, 'reason:', reason); // Don't exit immediately, try to log and continue console.error('Stack trace:', reason?.stack); }); // Memory leak prevention process.on('warning', (warning) => { console.warn('Node.js warning:', warning.name, warning.message); }); const app = express(); const PORT = process.env.PORT || 5000; // Trust proxy for rate limiting app.set('trust proxy', 1); // Rate limiting - Increased limits to prevent 429 errors const limiter = rateLimit({ windowMs: 15 * 60 * 1000, // 15 minutes max: 1000, // Increased from 100 to 1000 requests per windowMs message: { error: 'Too many requests, please try again later.' }, standardHeaders: true, legacyHeaders: false, skip: (req) => { // Skip rate limiting for health checks return req.path === '/health' || req.path === '/api/health'; } }); // Middleware app.use(cors()); app.use(express.json({ limit: '10mb' })); // Global request logger for debugging app.use((req, res, next) => { if (req.url.includes('tutorial-refinity')) { console.log('[Backend Global] Request:', req.method, req.url, req.path); } next(); }); app.use(limiter); // Database connection with better error handling // Normalize MongoDB URI for Atlas usage and ensure TLS in SRV connection const RAW_MONGO_URI = process.env.MONGODB_URI || 'mongodb://localhost:27017/transcreation-sandbox'; let NORMALIZED_MONGO_URI = RAW_MONGO_URI; try { if (RAW_MONGO_URI.startsWith('mongodb+srv://')) { const hasDb = /mongodb\+srv:\/\/[^/]+\//.test(RAW_MONGO_URI) && !/mongodb\+srv:\/\/[^/]+\/$/.test(RAW_MONGO_URI); if (!hasDb) { NORMALIZED_MONGO_URI = RAW_MONGO_URI.replace(/mongodb\+srv:\/\/([^/]+)\/?/, 'mongodb+srv://$1/transhub'); } if (!NORMALIZED_MONGO_URI.includes('tls=') && !NORMALIZED_MONGO_URI.includes('ssl=')) { NORMALIZED_MONGO_URI += (NORMALIZED_MONGO_URI.includes('?') ? '&' : '?') + 'tls=true'; } if (!NORMALIZED_MONGO_URI.includes('retryWrites=')) { NORMALIZED_MONGO_URI += (NORMALIZED_MONGO_URI.includes('?') ? '&' : '?') + 'retryWrites=true&w=majority'; } } } catch {} mongoose.connect(NORMALIZED_MONGO_URI, { maxPoolSize: 10, serverSelectionTimeoutMS: 15000, socketTimeoutMS: 45000, }) .then(() => { console.log('Connected to MongoDB'); }) .catch(err => { console.error('MongoDB connection error:', err); // Don't exit immediately, try to reconnect setTimeout(() => { console.log('Attempting to reconnect to MongoDB...'); mongoose.connect(NORMALIZED_MONGO_URI); }, 5000); }); // Handle MongoDB connection errors mongoose.connection.on('error', (err) => { console.error('MongoDB connection error:', err); }); mongoose.connection.on('disconnected', () => { console.log('MongoDB disconnected'); }); // Routes app.use('/api/auth', authRoutes); app.use('/api/source-texts', sourceTextRoutes); app.use('/api/submissions', submissionRoutes); app.use('/api/search', searchRoutes); app.use('/api/subtitles', subtitleRoutes); app.use('/api/subtitle-submissions', subtitleSubmissionRoutes); app.use('/api/weekly-practice-files', weeklyPracticeFilesRoutes); app.use('/api/dictionary', dictionaryRoutes); app.use('/api/iciba', icibaRoutes); app.use('/api/one', oneRoutes); app.use('/api/mt', mtRoutes); app.use('/api/slides', slidesRoutes); app.use('/api/messages', messagesRoutes); app.use('/api/links', linksRoutes); app.use('/api/docs', docsRoutes); app.use('/api/refinity', refinityRoutes); // Add logging middleware for ALL requests to help debug app.use((req, res, next) => { if (req.url.includes('tutorial-refinity')) { console.log('[Backend] ALL middleware - tutorial-refinity request:', req.method, req.url, req.path, 'headers:', req.headers['x-user-role']); } next(); }); app.use('/api/tutorial-refinity', (req, res, next) => { console.log('[Backend] tutorial-refinity middleware:', req.method, req.path, req.url, 'body:', req.body); next(); }, tutorialRefinityRoutes); app.use('/api', adminWeeksRoutes); // Health check endpoint app.get('/api/health', (req, res) => { res.json({ status: 'OK', message: 'TransHub API is running - Auth middleware fixed for time code editing' }); }); // Simple health check for Hugging Face Spaces app.get('/health', (req, res) => { res.status(200).send('OK'); }); // Error handling middleware app.use((err, req, res, next) => { console.error(err.stack); res.status(500).json({ error: 'Something went wrong!' }); }); app.listen(PORT, () => { console.log(`Server running on port ${PORT}`); // Seed default links if empty (best-effort) (async () => { try { const Link = require('./models/Link'); const count = await Link.countDocuments(); if (count === 0) { await Link.insertMany([ { title: 'AI工具集', url: 'https://ai-bot.cn', desc: 'AI tools directory and learning resources.' }, { title: 'Proz.com', url: 'https://www.proz.com', desc: 'Translators community and job marketplace.' }, { title: 'Translators without Borders', url: 'https://translatorswithoutborders.org', desc: 'Volunteer translation for humanitarian causes.' }, { title: 'TED Translator', url: 'https://www.ted.com/participate/translate', desc: 'Volunteer to translate TED talks.' }, { title: 'Matecat', url: 'https://www.matecat.com', desc: 'Free web-based CAT tool with TM/MT support.' } ]); console.log('Seeded default Useful Links'); } } catch (e) { console.warn('Unable to seed Useful Links:', e?.message); } })(); // Initialize week 1 tutorial tasks and weekly practice by default const initializeWeek1 = async () => { try { const SourceText = require('./models/SourceText'); // Check if week 1 tutorial tasks exist const existingTutorialTasks = await SourceText.find({ category: 'tutorial', weekNumber: 1 }); if (existingTutorialTasks.length === 0) { console.log('Initializing week 1 tutorial tasks...'); const tutorialTasks = [ { title: 'Tutorial Task 1 - Introduction', content: '欢迎来到我们的翻译课程。今天我们将学习如何翻译产品介绍。', category: 'tutorial', weekNumber: 1, sourceLanguage: 'Chinese', sourceCulture: 'Chinese', translationBrief: 'You have been asked to translate the following for marketing the product in a country where your LOTE is spoken.' }, { title: 'Tutorial Task 2 - Development', content: '这个产品具有独特的设计理念,融合了传统与现代元素。', category: 'tutorial', weekNumber: 1, sourceLanguage: 'Chinese', sourceCulture: 'Chinese', translationBrief: 'You have been asked to translate the following for marketing the product in a country where your LOTE is spoken.' }, { title: 'Tutorial Task 3 - Conclusion', content: '我们相信这个产品能够满足您的所有需求,为您提供最佳体验。', category: 'tutorial', weekNumber: 1, sourceLanguage: 'Chinese', sourceCulture: 'Chinese', translationBrief: 'You have been asked to translate the following for marketing the product in a country where your LOTE is spoken.' } ]; await SourceText.insertMany(tutorialTasks); console.log('Week 1 tutorial tasks initialized successfully'); } // Check if week 1 weekly practice exists const existingWeeklyPractice = await SourceText.find({ category: 'weekly-practice', weekNumber: 1 }); if (existingWeeklyPractice.length === 0) { console.log('Initializing week 1 weekly practice...'); const weeklyPractice = [ { title: 'Chinese Pun 1', content: '为什么睡前一定要吃夜宵?因为这样才不会做饿梦。', category: 'weekly-practice', weekNumber: 1, sourceLanguage: 'Chinese', sourceCulture: 'Chinese' }, { title: 'Chinese Pun 2', content: '女娲用什么补天?强扭的瓜。', category: 'weekly-practice', weekNumber: 1, sourceLanguage: 'Chinese', sourceCulture: 'Chinese' }, { title: 'Chinese Pun 3', content: '你知道如何区分真假大象吗?把他们仍进水中,真相会浮出水面的。', category: 'weekly-practice', weekNumber: 1, sourceLanguage: 'Chinese', sourceCulture: 'Chinese' }, { title: 'English Pun 1', content: 'What if Soy milk is just regular milk introducing itself in Spanish.', category: 'weekly-practice', weekNumber: 1, sourceLanguage: 'English', sourceCulture: 'Western' }, { title: 'English Pun 2', content: 'I can\'t believe I got fired from the calendar factory. All I did was take a day off.', category: 'weekly-practice', weekNumber: 1, sourceLanguage: 'English', sourceCulture: 'Western' }, { title: 'English Pun 3', content: 'When life gives you melons, you might be dyslexic.', category: 'weekly-practice', weekNumber: 1, sourceLanguage: 'English', sourceCulture: 'Western' } ]; await SourceText.insertMany(weeklyPractice); console.log('Week 1 weekly practice initialized successfully'); } } catch (error) { console.error('Error initializing week 1 data:', error); } }; // Auto-initialization disabled to prevent overwriting definitive data // initializeWeek1(); }); // Graceful shutdown process.on('SIGTERM', async () => { console.log('SIGTERM received, shutting down gracefully'); try { await mongoose.connection.close(); console.log('MongoDB connection closed'); process.exit(0); } catch (error) { console.error('Error closing MongoDB connection:', error); process.exit(1); } }); process.on('SIGINT', async () => { console.log('SIGINT received, shutting down gracefully'); try { await mongoose.connection.close(); console.log('MongoDB connection closed'); process.exit(0); } catch (error) { console.error('Error closing MongoDB connection:', error); process.exit(1); } });