Tristan Yu
feat(docs): add simple group doc API (list/create) and mount routes
921f144
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 linksRoutes = require('./routes/links');
const docsRoutes = require('./routes/docs');
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' }));
app.use(limiter);
// Database connection with better error handling
mongoose.connect(process.env.MONGODB_URI || 'mongodb://localhost:27017/transcreation-sandbox', {
maxPoolSize: 10,
serverSelectionTimeoutMS: 5000,
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(process.env.MONGODB_URI || 'mongodb://localhost:27017/transcreation-sandbox');
}, 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);
// Health check endpoint
app.get('/api/health', (req, res) => {
res.json({ status: 'OK', message: 'Transcreation Sandbox 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);
}
});