File size: 12,264 Bytes
da819ac
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
acfcd50
8811634
da819ac
 
60cbc7d
da819ac
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
8811634
 
 
 
 
 
 
da819ac
 
 
7ac268f
 
 
 
 
d6334fc
 
 
7ac268f
 
 
 
 
 
 
 
 
 
 
da819ac
7ac268f
da819ac
 
 
 
 
 
 
 
 
 
7ac268f
da819ac
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
acfcd50
8811634
 
 
 
 
 
 
 
 
 
 
60cbc7d
da819ac
 
 
faaf763
da819ac
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
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);
  }
});