Spaces:
Sleeping
Sleeping
| 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); | |
| } | |
| }); |