TransHub_backend / index.js
linguabot's picture
Upload folder using huggingface_hub
d6334fc verified
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);
}
});