Spaces:
Sleeping
Sleeping
Tristan Yu commited on
Commit ·
3a96436
0
Parent(s):
Initial backend deployment with improved stability
Browse files- .gitignore +62 -0
- Dockerfile +32 -0
- index.js +241 -0
- models/SourceText.js +45 -0
- models/Submission.js +153 -0
- models/User.js +65 -0
- monitor.js +49 -0
- package-lock.json +2065 -0
- package.json +25 -0
- routes/auth.js +783 -0
- routes/search.js +206 -0
- routes/sourceTexts.js +279 -0
- routes/submissions.js +703 -0
- stress-test.js +125 -0
.gitignore
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Dependencies
|
| 2 |
+
node_modules/
|
| 3 |
+
npm-debug.log*
|
| 4 |
+
yarn-debug.log*
|
| 5 |
+
yarn-error.log*
|
| 6 |
+
|
| 7 |
+
# Environment variables
|
| 8 |
+
.env
|
| 9 |
+
.env.local
|
| 10 |
+
.env.development.local
|
| 11 |
+
.env.test.local
|
| 12 |
+
.env.production.local
|
| 13 |
+
|
| 14 |
+
# Logs
|
| 15 |
+
logs/
|
| 16 |
+
*.log
|
| 17 |
+
|
| 18 |
+
# Runtime data
|
| 19 |
+
pids/
|
| 20 |
+
*.pid
|
| 21 |
+
*.seed
|
| 22 |
+
*.pid.lock
|
| 23 |
+
|
| 24 |
+
# Coverage directory used by tools like istanbul
|
| 25 |
+
coverage/
|
| 26 |
+
|
| 27 |
+
# nyc test coverage
|
| 28 |
+
.nyc_output
|
| 29 |
+
|
| 30 |
+
# Dependency directories
|
| 31 |
+
node_modules/
|
| 32 |
+
jspm_packages/
|
| 33 |
+
|
| 34 |
+
# Optional npm cache directory
|
| 35 |
+
.npm
|
| 36 |
+
|
| 37 |
+
# Optional REPL history
|
| 38 |
+
.node_repl_history
|
| 39 |
+
|
| 40 |
+
# Output of 'npm pack'
|
| 41 |
+
*.tgz
|
| 42 |
+
|
| 43 |
+
# Yarn Integrity file
|
| 44 |
+
.yarn-integrity
|
| 45 |
+
|
| 46 |
+
# dotenv environment variables file
|
| 47 |
+
.env
|
| 48 |
+
|
| 49 |
+
# IDE files
|
| 50 |
+
.vscode/
|
| 51 |
+
.idea/
|
| 52 |
+
*.swp
|
| 53 |
+
*.swo
|
| 54 |
+
|
| 55 |
+
# OS generated files
|
| 56 |
+
.DS_Store
|
| 57 |
+
.DS_Store?
|
| 58 |
+
._*
|
| 59 |
+
.Spotlight-V100
|
| 60 |
+
.Trashes
|
| 61 |
+
ehthumbs.db
|
| 62 |
+
Thumbs.db
|
Dockerfile
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Use Node.js 18 Alpine for smaller image size
|
| 2 |
+
FROM node:18-alpine
|
| 3 |
+
|
| 4 |
+
# Set working directory
|
| 5 |
+
WORKDIR /app
|
| 6 |
+
|
| 7 |
+
# Copy package files
|
| 8 |
+
COPY package*.json ./
|
| 9 |
+
|
| 10 |
+
# Install dependencies
|
| 11 |
+
RUN npm install --only=production
|
| 12 |
+
|
| 13 |
+
# Copy server source code
|
| 14 |
+
COPY . ./
|
| 15 |
+
|
| 16 |
+
# Create a non-root user
|
| 17 |
+
RUN addgroup -g 1001 -S nodejs
|
| 18 |
+
RUN adduser -S nodejs -u 1001
|
| 19 |
+
|
| 20 |
+
# Change ownership of the app directory
|
| 21 |
+
RUN chown -R nodejs:nodejs /app
|
| 22 |
+
USER nodejs
|
| 23 |
+
|
| 24 |
+
# Expose port
|
| 25 |
+
EXPOSE 5000
|
| 26 |
+
|
| 27 |
+
# Health check with longer start period and more retries
|
| 28 |
+
HEALTHCHECK --interval=60s --timeout=10s --start-period=120s --retries=5 \
|
| 29 |
+
CMD curl -f http://localhost:5000/api/health || exit 1
|
| 30 |
+
|
| 31 |
+
# Start the application
|
| 32 |
+
CMD ["npm", "start"]
|
index.js
ADDED
|
@@ -0,0 +1,241 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
const express = require('express');
|
| 2 |
+
const cors = require('cors');
|
| 3 |
+
const mongoose = require('mongoose');
|
| 4 |
+
const dotenv = require('dotenv');
|
| 5 |
+
const rateLimit = require('express-rate-limit');
|
| 6 |
+
|
| 7 |
+
// Import routes
|
| 8 |
+
const { router: authRoutes } = require('./routes/auth');
|
| 9 |
+
const sourceTextRoutes = require('./routes/sourceTexts');
|
| 10 |
+
const submissionRoutes = require('./routes/submissions');
|
| 11 |
+
const searchRoutes = require('./routes/search');
|
| 12 |
+
|
| 13 |
+
dotenv.config();
|
| 14 |
+
|
| 15 |
+
// Global error handlers to prevent crashes
|
| 16 |
+
process.on('uncaughtException', (error) => {
|
| 17 |
+
console.error('Uncaught Exception:', error);
|
| 18 |
+
// Don't exit immediately, try to log and continue
|
| 19 |
+
console.error('Stack trace:', error.stack);
|
| 20 |
+
});
|
| 21 |
+
|
| 22 |
+
process.on('unhandledRejection', (reason, promise) => {
|
| 23 |
+
console.error('Unhandled Rejection at:', promise, 'reason:', reason);
|
| 24 |
+
// Don't exit immediately, try to log and continue
|
| 25 |
+
console.error('Stack trace:', reason?.stack);
|
| 26 |
+
});
|
| 27 |
+
|
| 28 |
+
// Memory leak prevention
|
| 29 |
+
process.on('warning', (warning) => {
|
| 30 |
+
console.warn('Node.js warning:', warning.name, warning.message);
|
| 31 |
+
});
|
| 32 |
+
|
| 33 |
+
const app = express();
|
| 34 |
+
const PORT = process.env.PORT || 5000;
|
| 35 |
+
|
| 36 |
+
// Trust proxy for rate limiting
|
| 37 |
+
app.set('trust proxy', 1);
|
| 38 |
+
|
| 39 |
+
// Rate limiting
|
| 40 |
+
const limiter = rateLimit({
|
| 41 |
+
windowMs: 15 * 60 * 1000, // 15 minutes
|
| 42 |
+
max: 100 // limit each IP to 100 requests per windowMs
|
| 43 |
+
});
|
| 44 |
+
|
| 45 |
+
// Middleware
|
| 46 |
+
app.use(cors());
|
| 47 |
+
app.use(express.json({ limit: '10mb' }));
|
| 48 |
+
app.use(limiter);
|
| 49 |
+
|
| 50 |
+
// Database connection with better error handling
|
| 51 |
+
mongoose.connect(process.env.MONGODB_URI || 'mongodb://localhost:27017/transcreation-sandbox', {
|
| 52 |
+
maxPoolSize: 10,
|
| 53 |
+
serverSelectionTimeoutMS: 5000,
|
| 54 |
+
socketTimeoutMS: 45000,
|
| 55 |
+
})
|
| 56 |
+
.then(() => {
|
| 57 |
+
console.log('Connected to MongoDB');
|
| 58 |
+
})
|
| 59 |
+
.catch(err => {
|
| 60 |
+
console.error('MongoDB connection error:', err);
|
| 61 |
+
// Don't exit immediately, try to reconnect
|
| 62 |
+
setTimeout(() => {
|
| 63 |
+
console.log('Attempting to reconnect to MongoDB...');
|
| 64 |
+
mongoose.connect(process.env.MONGODB_URI || 'mongodb://localhost:27017/transcreation-sandbox');
|
| 65 |
+
}, 5000);
|
| 66 |
+
});
|
| 67 |
+
|
| 68 |
+
// Handle MongoDB connection errors
|
| 69 |
+
mongoose.connection.on('error', (err) => {
|
| 70 |
+
console.error('MongoDB connection error:', err);
|
| 71 |
+
});
|
| 72 |
+
|
| 73 |
+
mongoose.connection.on('disconnected', () => {
|
| 74 |
+
console.log('MongoDB disconnected');
|
| 75 |
+
});
|
| 76 |
+
|
| 77 |
+
// Routes
|
| 78 |
+
app.use('/api/auth', authRoutes);
|
| 79 |
+
app.use('/api/source-texts', sourceTextRoutes);
|
| 80 |
+
app.use('/api/submissions', submissionRoutes);
|
| 81 |
+
app.use('/api/search', searchRoutes);
|
| 82 |
+
|
| 83 |
+
// Health check endpoint
|
| 84 |
+
app.get('/api/health', (req, res) => {
|
| 85 |
+
res.json({ status: 'OK', message: 'Transcreation Sandbox API is running' });
|
| 86 |
+
});
|
| 87 |
+
|
| 88 |
+
// Simple health check for Hugging Face Spaces
|
| 89 |
+
app.get('/health', (req, res) => {
|
| 90 |
+
res.status(200).send('OK');
|
| 91 |
+
});
|
| 92 |
+
|
| 93 |
+
// Error handling middleware
|
| 94 |
+
app.use((err, req, res, next) => {
|
| 95 |
+
console.error(err.stack);
|
| 96 |
+
res.status(500).json({ error: 'Something went wrong!' });
|
| 97 |
+
});
|
| 98 |
+
|
| 99 |
+
app.listen(PORT, () => {
|
| 100 |
+
console.log(`Server running on port ${PORT}`);
|
| 101 |
+
|
| 102 |
+
// Initialize week 1 data after server starts
|
| 103 |
+
const initializeWeek1 = async () => {
|
| 104 |
+
try {
|
| 105 |
+
console.log('Starting week 1 data initialization...');
|
| 106 |
+
|
| 107 |
+
// Check if week 1 tutorial tasks exist
|
| 108 |
+
const existingTutorialTasks = await SourceText.find({
|
| 109 |
+
category: 'tutorial',
|
| 110 |
+
weekNumber: 1
|
| 111 |
+
});
|
| 112 |
+
|
| 113 |
+
if (existingTutorialTasks.length === 0) {
|
| 114 |
+
console.log('Initializing week 1 tutorial tasks...');
|
| 115 |
+
const tutorialTasks = [
|
| 116 |
+
{
|
| 117 |
+
title: 'Tutorial Task 1 - Introduction',
|
| 118 |
+
content: 'The opening paragraph establishes the main theme and provides essential context for the reader to understand the subsequent discussion.',
|
| 119 |
+
category: 'tutorial',
|
| 120 |
+
weekNumber: 1,
|
| 121 |
+
sourceLanguage: 'English',
|
| 122 |
+
sourceCulture: 'Western',
|
| 123 |
+
translationBrief: 'The municipal government of Xiamen plans to launch an international campaign to attract foreign investors and tourists. As part of the campaign, they\'re commissioning an English translation of a Chinese booklet about the city\'s history of foreign trade and cultural exchange, in addition to its tourist attractions. You\'ve been provided with the following excerpt for a test translation. They are aware that certain elements in the source text might not work well when translated into English for promotional purposes. So you\'re given the license to make changes where appropriate, but a list of such changes should be provided for the reference of the commissioner.'
|
| 124 |
+
},
|
| 125 |
+
{
|
| 126 |
+
title: 'Tutorial Task 2 - Development',
|
| 127 |
+
content: 'The second paragraph develops the argument further, providing supporting evidence and examples that reinforce the main points established in the opening section.',
|
| 128 |
+
category: 'tutorial',
|
| 129 |
+
weekNumber: 1,
|
| 130 |
+
sourceLanguage: 'English',
|
| 131 |
+
sourceCulture: 'Western',
|
| 132 |
+
translationBrief: 'The municipal government of Xiamen plans to launch an international campaign to attract foreign investors and tourists. As part of the campaign, they\'re commissioning an English translation of a Chinese booklet about the city\'s history of foreign trade and cultural exchange, in addition to its tourist attractions. You\'ve been provided with the following excerpt for a test translation. They are aware that certain elements in the source text might not work well when translated into English for promotional purposes. So you\'re given the license to make changes where appropriate, but a list of such changes should be provided for the reference of the commissioner.'
|
| 133 |
+
},
|
| 134 |
+
{
|
| 135 |
+
title: 'Tutorial Task 3 - Conclusion',
|
| 136 |
+
content: 'The concluding paragraph brings together all the key elements discussed throughout the text, offering a synthesis of the main ideas and leaving the reader with a clear understanding of the central message.',
|
| 137 |
+
category: 'tutorial',
|
| 138 |
+
weekNumber: 1,
|
| 139 |
+
sourceLanguage: 'English',
|
| 140 |
+
sourceCulture: 'Western',
|
| 141 |
+
translationBrief: 'The municipal government of Xiamen plans to launch an international campaign to attract foreign investors and tourists. As part of the campaign, they\'re commissioning an English translation of a Chinese booklet about the city\'s history of foreign trade and cultural exchange, in addition to its tourist attractions. You\'ve been provided with the following excerpt for a test translation. They are aware that certain elements in the source text might not work well when translated into English for promotional purposes. So you\'re given the license to make changes where appropriate, but a list of such changes should be provided for the reference of the commissioner.'
|
| 142 |
+
}
|
| 143 |
+
];
|
| 144 |
+
await SourceText.insertMany(tutorialTasks);
|
| 145 |
+
console.log('Week 1 tutorial tasks initialized successfully');
|
| 146 |
+
}
|
| 147 |
+
|
| 148 |
+
// Check if week 1 weekly practice exists
|
| 149 |
+
const existingWeeklyPractice = await SourceText.find({
|
| 150 |
+
category: 'weekly-practice',
|
| 151 |
+
weekNumber: 1
|
| 152 |
+
});
|
| 153 |
+
|
| 154 |
+
if (existingWeeklyPractice.length === 0) {
|
| 155 |
+
console.log('Initializing week 1 weekly practice...');
|
| 156 |
+
const weeklyPractice = [
|
| 157 |
+
{
|
| 158 |
+
title: 'Chinese Pun 1',
|
| 159 |
+
content: '为什么睡前一定要吃夜宵?因为这样才不会做饿梦。',
|
| 160 |
+
category: 'weekly-practice',
|
| 161 |
+
weekNumber: 1,
|
| 162 |
+
sourceLanguage: 'Chinese',
|
| 163 |
+
sourceCulture: 'Chinese'
|
| 164 |
+
},
|
| 165 |
+
{
|
| 166 |
+
title: 'Chinese Pun 2',
|
| 167 |
+
content: '女娲用什么补天?强扭的瓜。',
|
| 168 |
+
category: 'weekly-practice',
|
| 169 |
+
weekNumber: 1,
|
| 170 |
+
sourceLanguage: 'Chinese',
|
| 171 |
+
sourceCulture: 'Chinese'
|
| 172 |
+
},
|
| 173 |
+
{
|
| 174 |
+
title: 'English Pun 1',
|
| 175 |
+
content: 'Why do we drive on a parkway and park on a driveway?',
|
| 176 |
+
category: 'weekly-practice',
|
| 177 |
+
weekNumber: 1,
|
| 178 |
+
sourceLanguage: 'English',
|
| 179 |
+
sourceCulture: 'Western'
|
| 180 |
+
},
|
| 181 |
+
{
|
| 182 |
+
title: 'English Pun 2',
|
| 183 |
+
content: 'I can\'t believe I got fired from the calendar factory. All I did was take a day off.',
|
| 184 |
+
category: 'weekly-practice',
|
| 185 |
+
weekNumber: 1,
|
| 186 |
+
sourceLanguage: 'English',
|
| 187 |
+
sourceCulture: 'Western'
|
| 188 |
+
}
|
| 189 |
+
];
|
| 190 |
+
await SourceText.insertMany(weeklyPractice);
|
| 191 |
+
console.log('Week 1 weekly practice initialized successfully');
|
| 192 |
+
}
|
| 193 |
+
|
| 194 |
+
console.log('Week 1 data initialization completed successfully');
|
| 195 |
+
} catch (error) {
|
| 196 |
+
console.error('Error initializing week 1 data:', error);
|
| 197 |
+
}
|
| 198 |
+
};
|
| 199 |
+
|
| 200 |
+
// Start initialization after a short delay
|
| 201 |
+
setTimeout(initializeWeek1, 1000);
|
| 202 |
+
|
| 203 |
+
// Keep-alive mechanism
|
| 204 |
+
setInterval(() => {
|
| 205 |
+
console.log('Server heartbeat - still running');
|
| 206 |
+
}, 300000); // Log every 5 minutes
|
| 207 |
+
});
|
| 208 |
+
|
| 209 |
+
// Graceful shutdown
|
| 210 |
+
process.on('SIGTERM', async () => {
|
| 211 |
+
console.log('SIGTERM received, shutting down gracefully');
|
| 212 |
+
try {
|
| 213 |
+
if (mongoose.connection.readyState === 1) {
|
| 214 |
+
await mongoose.connection.close();
|
| 215 |
+
console.log('MongoDB connection closed');
|
| 216 |
+
}
|
| 217 |
+
process.exit(0);
|
| 218 |
+
} catch (error) {
|
| 219 |
+
console.error('Error closing MongoDB connection:', error);
|
| 220 |
+
process.exit(1);
|
| 221 |
+
}
|
| 222 |
+
});
|
| 223 |
+
|
| 224 |
+
process.on('SIGINT', async () => {
|
| 225 |
+
console.log('SIGINT received, shutting down gracefully');
|
| 226 |
+
try {
|
| 227 |
+
if (mongoose.connection.readyState === 1) {
|
| 228 |
+
await mongoose.connection.close();
|
| 229 |
+
console.log('MongoDB connection closed');
|
| 230 |
+
}
|
| 231 |
+
process.exit(0);
|
| 232 |
+
} catch (error) {
|
| 233 |
+
console.error('Error closing MongoDB connection:', error);
|
| 234 |
+
process.exit(1);
|
| 235 |
+
}
|
| 236 |
+
});
|
| 237 |
+
|
| 238 |
+
// Keep the process alive
|
| 239 |
+
process.on('exit', (code) => {
|
| 240 |
+
console.log(`Process exiting with code: ${code}`);
|
| 241 |
+
});
|
models/SourceText.js
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
const mongoose = require('mongoose');
|
| 2 |
+
|
| 3 |
+
const culturalElementSchema = new mongoose.Schema({
|
| 4 |
+
element: { type: String, required: true },
|
| 5 |
+
description: { type: String, required: true },
|
| 6 |
+
significance: { type: String, required: true }
|
| 7 |
+
});
|
| 8 |
+
|
| 9 |
+
const sourceTextSchema = new mongoose.Schema({
|
| 10 |
+
title: { type: String, required: true },
|
| 11 |
+
content: { type: String, required: true },
|
| 12 |
+
sourceLanguage: { type: String, required: true },
|
| 13 |
+
|
| 14 |
+
sourceType: {
|
| 15 |
+
type: String,
|
| 16 |
+
enum: ['api', 'manual', 'practice', 'tutorial', 'weekly-practice'],
|
| 17 |
+
default: 'manual'
|
| 18 |
+
},
|
| 19 |
+
category: {
|
| 20 |
+
type: String,
|
| 21 |
+
enum: ['practice', 'tutorial', 'weekly-practice'],
|
| 22 |
+
required: true
|
| 23 |
+
},
|
| 24 |
+
weekNumber: {
|
| 25 |
+
type: Number,
|
| 26 |
+
required: function() { return this.category !== 'practice'; }
|
| 27 |
+
},
|
| 28 |
+
translationBrief: { type: String },
|
| 29 |
+
culturalElements: [culturalElementSchema],
|
| 30 |
+
difficulty: {
|
| 31 |
+
type: String,
|
| 32 |
+
enum: ['beginner', 'intermediate', 'advanced'],
|
| 33 |
+
default: 'intermediate'
|
| 34 |
+
},
|
| 35 |
+
tags: [String],
|
| 36 |
+
targetCultures: [String],
|
| 37 |
+
isActive: { type: Boolean, default: true },
|
| 38 |
+
usageCount: { type: Number, default: 0 },
|
| 39 |
+
averageRating: { type: Number, default: 0 },
|
| 40 |
+
ratingCount: { type: Number, default: 0 }
|
| 41 |
+
}, {
|
| 42 |
+
timestamps: true
|
| 43 |
+
});
|
| 44 |
+
|
| 45 |
+
module.exports = mongoose.model('SourceText', sourceTextSchema);
|
models/Submission.js
ADDED
|
@@ -0,0 +1,153 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
const mongoose = require('mongoose');
|
| 2 |
+
|
| 3 |
+
const feedbackSchema = new mongoose.Schema({
|
| 4 |
+
userId: {
|
| 5 |
+
type: mongoose.Schema.Types.ObjectId,
|
| 6 |
+
ref: 'User',
|
| 7 |
+
required: true
|
| 8 |
+
},
|
| 9 |
+
comment: {
|
| 10 |
+
type: String,
|
| 11 |
+
required: true,
|
| 12 |
+
trim: true
|
| 13 |
+
},
|
| 14 |
+
rating: {
|
| 15 |
+
type: Number,
|
| 16 |
+
min: 1,
|
| 17 |
+
max: 5
|
| 18 |
+
},
|
| 19 |
+
createdAt: {
|
| 20 |
+
type: Date,
|
| 21 |
+
default: Date.now
|
| 22 |
+
}
|
| 23 |
+
});
|
| 24 |
+
|
| 25 |
+
const voteSchema = new mongoose.Schema({
|
| 26 |
+
userId: {
|
| 27 |
+
type: mongoose.Schema.Types.ObjectId,
|
| 28 |
+
ref: 'User',
|
| 29 |
+
required: true
|
| 30 |
+
},
|
| 31 |
+
rank: {
|
| 32 |
+
type: Number,
|
| 33 |
+
enum: [1, 2, 3], // 1 = 1st place, 2 = 2nd place, 3 = 3rd place
|
| 34 |
+
required: true
|
| 35 |
+
},
|
| 36 |
+
createdAt: {
|
| 37 |
+
type: Date,
|
| 38 |
+
default: Date.now
|
| 39 |
+
}
|
| 40 |
+
});
|
| 41 |
+
|
| 42 |
+
const submissionSchema = new mongoose.Schema({
|
| 43 |
+
sourceTextId: {
|
| 44 |
+
type: mongoose.Schema.Types.ObjectId,
|
| 45 |
+
ref: 'SourceText',
|
| 46 |
+
required: true
|
| 47 |
+
},
|
| 48 |
+
userId: {
|
| 49 |
+
type: mongoose.Schema.Types.ObjectId,
|
| 50 |
+
ref: 'User',
|
| 51 |
+
required: true
|
| 52 |
+
},
|
| 53 |
+
username: {
|
| 54 |
+
type: String,
|
| 55 |
+
required: true
|
| 56 |
+
},
|
| 57 |
+
groupNumber: {
|
| 58 |
+
type: Number,
|
| 59 |
+
min: 1,
|
| 60 |
+
max: 8,
|
| 61 |
+
required: function() {
|
| 62 |
+
// Group number is required for tutorial tasks, optional for other submissions
|
| 63 |
+
return this.sourceTextId && this.sourceTextId.category === 'tutorial';
|
| 64 |
+
}
|
| 65 |
+
},
|
| 66 |
+
targetCulture: {
|
| 67 |
+
type: String,
|
| 68 |
+
required: true
|
| 69 |
+
},
|
| 70 |
+
targetLanguage: {
|
| 71 |
+
type: String,
|
| 72 |
+
required: true
|
| 73 |
+
},
|
| 74 |
+
transcreation: {
|
| 75 |
+
type: String,
|
| 76 |
+
required: true
|
| 77 |
+
},
|
| 78 |
+
explanation: {
|
| 79 |
+
type: String,
|
| 80 |
+
required: true
|
| 81 |
+
},
|
| 82 |
+
culturalAdaptations: [{
|
| 83 |
+
type: String
|
| 84 |
+
}],
|
| 85 |
+
isAnonymous: {
|
| 86 |
+
type: Boolean,
|
| 87 |
+
default: true
|
| 88 |
+
},
|
| 89 |
+
status: {
|
| 90 |
+
type: String,
|
| 91 |
+
enum: ['draft', 'submitted', 'reviewed', 'approved', 'rejected'],
|
| 92 |
+
default: 'submitted'
|
| 93 |
+
},
|
| 94 |
+
difficulty: {
|
| 95 |
+
type: String,
|
| 96 |
+
enum: ['beginner', 'intermediate', 'advanced'],
|
| 97 |
+
default: 'intermediate'
|
| 98 |
+
},
|
| 99 |
+
votes: [voteSchema],
|
| 100 |
+
feedback: [{
|
| 101 |
+
userId: {
|
| 102 |
+
type: mongoose.Schema.Types.ObjectId,
|
| 103 |
+
ref: 'User'
|
| 104 |
+
},
|
| 105 |
+
comment: String,
|
| 106 |
+
createdAt: {
|
| 107 |
+
type: Date,
|
| 108 |
+
default: Date.now
|
| 109 |
+
}
|
| 110 |
+
}],
|
| 111 |
+
createdAt: {
|
| 112 |
+
type: Date,
|
| 113 |
+
default: Date.now
|
| 114 |
+
},
|
| 115 |
+
updatedAt: {
|
| 116 |
+
type: Date,
|
| 117 |
+
default: Date.now
|
| 118 |
+
}
|
| 119 |
+
});
|
| 120 |
+
|
| 121 |
+
// Calculate score based on votes (1st place = 3 points, 2nd place = 2 points, 3rd place = 1 point)
|
| 122 |
+
submissionSchema.methods.calculateScore = function() {
|
| 123 |
+
return this.votes.reduce((total, vote) => {
|
| 124 |
+
const points = 4 - vote.rank; // 1st = 3 points, 2nd = 2 points, 3rd = 1 point
|
| 125 |
+
return total + points;
|
| 126 |
+
}, 0);
|
| 127 |
+
};
|
| 128 |
+
|
| 129 |
+
// Get vote count by rank
|
| 130 |
+
submissionSchema.methods.getVoteCountByRank = function() {
|
| 131 |
+
const counts = { 1: 0, 2: 0, 3: 0 };
|
| 132 |
+
this.votes.forEach(vote => {
|
| 133 |
+
counts[vote.rank]++;
|
| 134 |
+
});
|
| 135 |
+
return counts;
|
| 136 |
+
};
|
| 137 |
+
|
| 138 |
+
// Update score before saving
|
| 139 |
+
submissionSchema.pre('save', function(next) {
|
| 140 |
+
this.score = this.calculateScore();
|
| 141 |
+
this.updatedAt = Date.now();
|
| 142 |
+
next();
|
| 143 |
+
});
|
| 144 |
+
|
| 145 |
+
// Index for efficient querying
|
| 146 |
+
submissionSchema.index({
|
| 147 |
+
sourceTextId: 1,
|
| 148 |
+
targetCulture: 1,
|
| 149 |
+
status: 1,
|
| 150 |
+
createdAt: -1
|
| 151 |
+
});
|
| 152 |
+
|
| 153 |
+
module.exports = mongoose.model('Submission', submissionSchema);
|
models/User.js
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
const mongoose = require('mongoose');
|
| 2 |
+
const bcrypt = require('bcryptjs');
|
| 3 |
+
|
| 4 |
+
const userSchema = new mongoose.Schema({
|
| 5 |
+
username: {
|
| 6 |
+
type: String,
|
| 7 |
+
required: true,
|
| 8 |
+
unique: true,
|
| 9 |
+
trim: true,
|
| 10 |
+
minlength: 3
|
| 11 |
+
},
|
| 12 |
+
email: {
|
| 13 |
+
type: String,
|
| 14 |
+
required: true,
|
| 15 |
+
unique: true,
|
| 16 |
+
trim: true,
|
| 17 |
+
lowercase: true
|
| 18 |
+
},
|
| 19 |
+
password: {
|
| 20 |
+
type: String,
|
| 21 |
+
required: true,
|
| 22 |
+
minlength: 6
|
| 23 |
+
},
|
| 24 |
+
role: {
|
| 25 |
+
type: String,
|
| 26 |
+
enum: ['student', 'instructor', 'admin'],
|
| 27 |
+
default: 'student'
|
| 28 |
+
},
|
| 29 |
+
targetCultures: [{
|
| 30 |
+
type: String,
|
| 31 |
+
trim: true
|
| 32 |
+
}],
|
| 33 |
+
nativeLanguage: {
|
| 34 |
+
type: String,
|
| 35 |
+
trim: true
|
| 36 |
+
},
|
| 37 |
+
createdAt: {
|
| 38 |
+
type: Date,
|
| 39 |
+
default: Date.now
|
| 40 |
+
},
|
| 41 |
+
lastActive: {
|
| 42 |
+
type: Date,
|
| 43 |
+
default: Date.now
|
| 44 |
+
}
|
| 45 |
+
});
|
| 46 |
+
|
| 47 |
+
// Hash password before saving
|
| 48 |
+
userSchema.pre('save', async function(next) {
|
| 49 |
+
if (!this.isModified('password')) return next();
|
| 50 |
+
|
| 51 |
+
try {
|
| 52 |
+
const salt = await bcrypt.genSalt(10);
|
| 53 |
+
this.password = await bcrypt.hash(this.password, salt);
|
| 54 |
+
next();
|
| 55 |
+
} catch (error) {
|
| 56 |
+
next(error);
|
| 57 |
+
}
|
| 58 |
+
});
|
| 59 |
+
|
| 60 |
+
// Method to compare passwords
|
| 61 |
+
userSchema.methods.comparePassword = async function(candidatePassword) {
|
| 62 |
+
return bcrypt.compare(candidatePassword, this.password);
|
| 63 |
+
};
|
| 64 |
+
|
| 65 |
+
module.exports = mongoose.model('User', userSchema);
|
monitor.js
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
const http = require('http');
|
| 2 |
+
|
| 3 |
+
function checkServerHealth() {
|
| 4 |
+
const options = {
|
| 5 |
+
hostname: 'localhost',
|
| 6 |
+
port: 5000,
|
| 7 |
+
path: '/api/health',
|
| 8 |
+
method: 'GET',
|
| 9 |
+
timeout: 5000
|
| 10 |
+
};
|
| 11 |
+
|
| 12 |
+
const req = http.request(options, (res) => {
|
| 13 |
+
let data = '';
|
| 14 |
+
res.on('data', (chunk) => {
|
| 15 |
+
data += chunk;
|
| 16 |
+
});
|
| 17 |
+
res.on('end', () => {
|
| 18 |
+
try {
|
| 19 |
+
const response = JSON.parse(data);
|
| 20 |
+
if (response.status === 'OK') {
|
| 21 |
+
console.log(`[${new Date().toISOString()}] Server is healthy`);
|
| 22 |
+
} else {
|
| 23 |
+
console.error(`[${new Date().toISOString()}] Server returned unexpected status:`, response);
|
| 24 |
+
}
|
| 25 |
+
} catch (error) {
|
| 26 |
+
console.error(`[${new Date().toISOString()}] Failed to parse health check response:`, error);
|
| 27 |
+
}
|
| 28 |
+
});
|
| 29 |
+
});
|
| 30 |
+
|
| 31 |
+
req.on('error', (error) => {
|
| 32 |
+
console.error(`[${new Date().toISOString()}] Server health check failed:`, error.message);
|
| 33 |
+
});
|
| 34 |
+
|
| 35 |
+
req.on('timeout', () => {
|
| 36 |
+
console.error(`[${new Date().toISOString()}] Server health check timed out`);
|
| 37 |
+
req.destroy();
|
| 38 |
+
});
|
| 39 |
+
|
| 40 |
+
req.end();
|
| 41 |
+
}
|
| 42 |
+
|
| 43 |
+
// Check server health every 30 seconds
|
| 44 |
+
setInterval(checkServerHealth, 30000);
|
| 45 |
+
|
| 46 |
+
// Initial check
|
| 47 |
+
checkServerHealth();
|
| 48 |
+
|
| 49 |
+
console.log('Server monitoring started. Health checks every 30 seconds.');
|
package-lock.json
ADDED
|
@@ -0,0 +1,2065 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"name": "transcreation-sandbox-server",
|
| 3 |
+
"version": "1.0.0",
|
| 4 |
+
"lockfileVersion": 3,
|
| 5 |
+
"requires": true,
|
| 6 |
+
"packages": {
|
| 7 |
+
"": {
|
| 8 |
+
"name": "transcreation-sandbox-server",
|
| 9 |
+
"version": "1.0.0",
|
| 10 |
+
"dependencies": {
|
| 11 |
+
"axios": "^1.6.2",
|
| 12 |
+
"bcryptjs": "^2.4.3",
|
| 13 |
+
"cheerio": "^1.0.0-rc.12",
|
| 14 |
+
"cors": "^2.8.5",
|
| 15 |
+
"dotenv": "^16.3.1",
|
| 16 |
+
"express": "^4.18.2",
|
| 17 |
+
"express-rate-limit": "^7.1.5",
|
| 18 |
+
"express-validator": "^7.2.1",
|
| 19 |
+
"jsonwebtoken": "^9.0.2",
|
| 20 |
+
"mongoose": "^8.0.3",
|
| 21 |
+
"uuid": "^9.0.1"
|
| 22 |
+
},
|
| 23 |
+
"devDependencies": {
|
| 24 |
+
"nodemon": "^3.0.2"
|
| 25 |
+
}
|
| 26 |
+
},
|
| 27 |
+
"node_modules/@mongodb-js/saslprep": {
|
| 28 |
+
"version": "1.3.0",
|
| 29 |
+
"resolved": "https://registry.npmjs.org/@mongodb-js/saslprep/-/saslprep-1.3.0.tgz",
|
| 30 |
+
"integrity": "sha512-zlayKCsIjYb7/IdfqxorK5+xUMyi4vOKcFy10wKJYc63NSdKI8mNME+uJqfatkPmOSMMUiojrL58IePKBm3gvQ==",
|
| 31 |
+
"license": "MIT",
|
| 32 |
+
"dependencies": {
|
| 33 |
+
"sparse-bitfield": "^3.0.3"
|
| 34 |
+
}
|
| 35 |
+
},
|
| 36 |
+
"node_modules/@types/webidl-conversions": {
|
| 37 |
+
"version": "7.0.3",
|
| 38 |
+
"resolved": "https://registry.npmjs.org/@types/webidl-conversions/-/webidl-conversions-7.0.3.tgz",
|
| 39 |
+
"integrity": "sha512-CiJJvcRtIgzadHCYXw7dqEnMNRjhGZlYK05Mj9OyktqV8uVT8fD2BFOB7S1uwBE3Kj2Z+4UyPmFw/Ixgw/LAlA==",
|
| 40 |
+
"license": "MIT"
|
| 41 |
+
},
|
| 42 |
+
"node_modules/@types/whatwg-url": {
|
| 43 |
+
"version": "11.0.5",
|
| 44 |
+
"resolved": "https://registry.npmjs.org/@types/whatwg-url/-/whatwg-url-11.0.5.tgz",
|
| 45 |
+
"integrity": "sha512-coYR071JRaHa+xoEvvYqvnIHaVqaYrLPbsufM9BF63HkwI5Lgmy2QR8Q5K/lYDYo5AK82wOvSOS0UsLTpTG7uQ==",
|
| 46 |
+
"license": "MIT",
|
| 47 |
+
"dependencies": {
|
| 48 |
+
"@types/webidl-conversions": "*"
|
| 49 |
+
}
|
| 50 |
+
},
|
| 51 |
+
"node_modules/accepts": {
|
| 52 |
+
"version": "1.3.8",
|
| 53 |
+
"resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz",
|
| 54 |
+
"integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==",
|
| 55 |
+
"license": "MIT",
|
| 56 |
+
"dependencies": {
|
| 57 |
+
"mime-types": "~2.1.34",
|
| 58 |
+
"negotiator": "0.6.3"
|
| 59 |
+
},
|
| 60 |
+
"engines": {
|
| 61 |
+
"node": ">= 0.6"
|
| 62 |
+
}
|
| 63 |
+
},
|
| 64 |
+
"node_modules/anymatch": {
|
| 65 |
+
"version": "3.1.3",
|
| 66 |
+
"resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz",
|
| 67 |
+
"integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==",
|
| 68 |
+
"dev": true,
|
| 69 |
+
"license": "ISC",
|
| 70 |
+
"dependencies": {
|
| 71 |
+
"normalize-path": "^3.0.0",
|
| 72 |
+
"picomatch": "^2.0.4"
|
| 73 |
+
},
|
| 74 |
+
"engines": {
|
| 75 |
+
"node": ">= 8"
|
| 76 |
+
}
|
| 77 |
+
},
|
| 78 |
+
"node_modules/array-flatten": {
|
| 79 |
+
"version": "1.1.1",
|
| 80 |
+
"resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz",
|
| 81 |
+
"integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==",
|
| 82 |
+
"license": "MIT"
|
| 83 |
+
},
|
| 84 |
+
"node_modules/asynckit": {
|
| 85 |
+
"version": "0.4.0",
|
| 86 |
+
"resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
|
| 87 |
+
"integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==",
|
| 88 |
+
"license": "MIT"
|
| 89 |
+
},
|
| 90 |
+
"node_modules/axios": {
|
| 91 |
+
"version": "1.11.0",
|
| 92 |
+
"resolved": "https://registry.npmjs.org/axios/-/axios-1.11.0.tgz",
|
| 93 |
+
"integrity": "sha512-1Lx3WLFQWm3ooKDYZD1eXmoGO9fxYQjrycfHFC8P0sCfQVXyROp0p9PFWBehewBOdCwHc+f/b8I0fMto5eSfwA==",
|
| 94 |
+
"license": "MIT",
|
| 95 |
+
"dependencies": {
|
| 96 |
+
"follow-redirects": "^1.15.6",
|
| 97 |
+
"form-data": "^4.0.4",
|
| 98 |
+
"proxy-from-env": "^1.1.0"
|
| 99 |
+
}
|
| 100 |
+
},
|
| 101 |
+
"node_modules/balanced-match": {
|
| 102 |
+
"version": "1.0.2",
|
| 103 |
+
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
|
| 104 |
+
"integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
|
| 105 |
+
"dev": true,
|
| 106 |
+
"license": "MIT"
|
| 107 |
+
},
|
| 108 |
+
"node_modules/bcryptjs": {
|
| 109 |
+
"version": "2.4.3",
|
| 110 |
+
"resolved": "https://registry.npmjs.org/bcryptjs/-/bcryptjs-2.4.3.tgz",
|
| 111 |
+
"integrity": "sha512-V/Hy/X9Vt7f3BbPJEi8BdVFMByHi+jNXrYkW3huaybV/kQ0KJg0Y6PkEMbn+zeT+i+SiKZ/HMqJGIIt4LZDqNQ==",
|
| 112 |
+
"license": "MIT"
|
| 113 |
+
},
|
| 114 |
+
"node_modules/binary-extensions": {
|
| 115 |
+
"version": "2.3.0",
|
| 116 |
+
"resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz",
|
| 117 |
+
"integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==",
|
| 118 |
+
"dev": true,
|
| 119 |
+
"license": "MIT",
|
| 120 |
+
"engines": {
|
| 121 |
+
"node": ">=8"
|
| 122 |
+
},
|
| 123 |
+
"funding": {
|
| 124 |
+
"url": "https://github.com/sponsors/sindresorhus"
|
| 125 |
+
}
|
| 126 |
+
},
|
| 127 |
+
"node_modules/body-parser": {
|
| 128 |
+
"version": "1.20.3",
|
| 129 |
+
"resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.3.tgz",
|
| 130 |
+
"integrity": "sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g==",
|
| 131 |
+
"license": "MIT",
|
| 132 |
+
"dependencies": {
|
| 133 |
+
"bytes": "3.1.2",
|
| 134 |
+
"content-type": "~1.0.5",
|
| 135 |
+
"debug": "2.6.9",
|
| 136 |
+
"depd": "2.0.0",
|
| 137 |
+
"destroy": "1.2.0",
|
| 138 |
+
"http-errors": "2.0.0",
|
| 139 |
+
"iconv-lite": "0.4.24",
|
| 140 |
+
"on-finished": "2.4.1",
|
| 141 |
+
"qs": "6.13.0",
|
| 142 |
+
"raw-body": "2.5.2",
|
| 143 |
+
"type-is": "~1.6.18",
|
| 144 |
+
"unpipe": "1.0.0"
|
| 145 |
+
},
|
| 146 |
+
"engines": {
|
| 147 |
+
"node": ">= 0.8",
|
| 148 |
+
"npm": "1.2.8000 || >= 1.4.16"
|
| 149 |
+
}
|
| 150 |
+
},
|
| 151 |
+
"node_modules/body-parser/node_modules/iconv-lite": {
|
| 152 |
+
"version": "0.4.24",
|
| 153 |
+
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz",
|
| 154 |
+
"integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==",
|
| 155 |
+
"license": "MIT",
|
| 156 |
+
"dependencies": {
|
| 157 |
+
"safer-buffer": ">= 2.1.2 < 3"
|
| 158 |
+
},
|
| 159 |
+
"engines": {
|
| 160 |
+
"node": ">=0.10.0"
|
| 161 |
+
}
|
| 162 |
+
},
|
| 163 |
+
"node_modules/boolbase": {
|
| 164 |
+
"version": "1.0.0",
|
| 165 |
+
"resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz",
|
| 166 |
+
"integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==",
|
| 167 |
+
"license": "ISC"
|
| 168 |
+
},
|
| 169 |
+
"node_modules/brace-expansion": {
|
| 170 |
+
"version": "1.1.12",
|
| 171 |
+
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz",
|
| 172 |
+
"integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==",
|
| 173 |
+
"dev": true,
|
| 174 |
+
"license": "MIT",
|
| 175 |
+
"dependencies": {
|
| 176 |
+
"balanced-match": "^1.0.0",
|
| 177 |
+
"concat-map": "0.0.1"
|
| 178 |
+
}
|
| 179 |
+
},
|
| 180 |
+
"node_modules/braces": {
|
| 181 |
+
"version": "3.0.3",
|
| 182 |
+
"resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz",
|
| 183 |
+
"integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==",
|
| 184 |
+
"dev": true,
|
| 185 |
+
"license": "MIT",
|
| 186 |
+
"dependencies": {
|
| 187 |
+
"fill-range": "^7.1.1"
|
| 188 |
+
},
|
| 189 |
+
"engines": {
|
| 190 |
+
"node": ">=8"
|
| 191 |
+
}
|
| 192 |
+
},
|
| 193 |
+
"node_modules/bson": {
|
| 194 |
+
"version": "6.10.4",
|
| 195 |
+
"resolved": "https://registry.npmjs.org/bson/-/bson-6.10.4.tgz",
|
| 196 |
+
"integrity": "sha512-WIsKqkSC0ABoBJuT1LEX+2HEvNmNKKgnTAyd0fL8qzK4SH2i9NXg+t08YtdZp/V9IZ33cxe3iV4yM0qg8lMQng==",
|
| 197 |
+
"license": "Apache-2.0",
|
| 198 |
+
"engines": {
|
| 199 |
+
"node": ">=16.20.1"
|
| 200 |
+
}
|
| 201 |
+
},
|
| 202 |
+
"node_modules/buffer-equal-constant-time": {
|
| 203 |
+
"version": "1.0.1",
|
| 204 |
+
"resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz",
|
| 205 |
+
"integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==",
|
| 206 |
+
"license": "BSD-3-Clause"
|
| 207 |
+
},
|
| 208 |
+
"node_modules/bytes": {
|
| 209 |
+
"version": "3.1.2",
|
| 210 |
+
"resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz",
|
| 211 |
+
"integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==",
|
| 212 |
+
"license": "MIT",
|
| 213 |
+
"engines": {
|
| 214 |
+
"node": ">= 0.8"
|
| 215 |
+
}
|
| 216 |
+
},
|
| 217 |
+
"node_modules/call-bind-apply-helpers": {
|
| 218 |
+
"version": "1.0.2",
|
| 219 |
+
"resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz",
|
| 220 |
+
"integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==",
|
| 221 |
+
"license": "MIT",
|
| 222 |
+
"dependencies": {
|
| 223 |
+
"es-errors": "^1.3.0",
|
| 224 |
+
"function-bind": "^1.1.2"
|
| 225 |
+
},
|
| 226 |
+
"engines": {
|
| 227 |
+
"node": ">= 0.4"
|
| 228 |
+
}
|
| 229 |
+
},
|
| 230 |
+
"node_modules/call-bound": {
|
| 231 |
+
"version": "1.0.4",
|
| 232 |
+
"resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz",
|
| 233 |
+
"integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==",
|
| 234 |
+
"license": "MIT",
|
| 235 |
+
"dependencies": {
|
| 236 |
+
"call-bind-apply-helpers": "^1.0.2",
|
| 237 |
+
"get-intrinsic": "^1.3.0"
|
| 238 |
+
},
|
| 239 |
+
"engines": {
|
| 240 |
+
"node": ">= 0.4"
|
| 241 |
+
},
|
| 242 |
+
"funding": {
|
| 243 |
+
"url": "https://github.com/sponsors/ljharb"
|
| 244 |
+
}
|
| 245 |
+
},
|
| 246 |
+
"node_modules/cheerio": {
|
| 247 |
+
"version": "1.1.2",
|
| 248 |
+
"resolved": "https://registry.npmjs.org/cheerio/-/cheerio-1.1.2.tgz",
|
| 249 |
+
"integrity": "sha512-IkxPpb5rS/d1IiLbHMgfPuS0FgiWTtFIm/Nj+2woXDLTZ7fOT2eqzgYbdMlLweqlHbsZjxEChoVK+7iph7jyQg==",
|
| 250 |
+
"license": "MIT",
|
| 251 |
+
"dependencies": {
|
| 252 |
+
"cheerio-select": "^2.1.0",
|
| 253 |
+
"dom-serializer": "^2.0.0",
|
| 254 |
+
"domhandler": "^5.0.3",
|
| 255 |
+
"domutils": "^3.2.2",
|
| 256 |
+
"encoding-sniffer": "^0.2.1",
|
| 257 |
+
"htmlparser2": "^10.0.0",
|
| 258 |
+
"parse5": "^7.3.0",
|
| 259 |
+
"parse5-htmlparser2-tree-adapter": "^7.1.0",
|
| 260 |
+
"parse5-parser-stream": "^7.1.2",
|
| 261 |
+
"undici": "^7.12.0",
|
| 262 |
+
"whatwg-mimetype": "^4.0.0"
|
| 263 |
+
},
|
| 264 |
+
"engines": {
|
| 265 |
+
"node": ">=20.18.1"
|
| 266 |
+
},
|
| 267 |
+
"funding": {
|
| 268 |
+
"url": "https://github.com/cheeriojs/cheerio?sponsor=1"
|
| 269 |
+
}
|
| 270 |
+
},
|
| 271 |
+
"node_modules/cheerio-select": {
|
| 272 |
+
"version": "2.1.0",
|
| 273 |
+
"resolved": "https://registry.npmjs.org/cheerio-select/-/cheerio-select-2.1.0.tgz",
|
| 274 |
+
"integrity": "sha512-9v9kG0LvzrlcungtnJtpGNxY+fzECQKhK4EGJX2vByejiMX84MFNQw4UxPJl3bFbTMw+Dfs37XaIkCwTZfLh4g==",
|
| 275 |
+
"license": "BSD-2-Clause",
|
| 276 |
+
"dependencies": {
|
| 277 |
+
"boolbase": "^1.0.0",
|
| 278 |
+
"css-select": "^5.1.0",
|
| 279 |
+
"css-what": "^6.1.0",
|
| 280 |
+
"domelementtype": "^2.3.0",
|
| 281 |
+
"domhandler": "^5.0.3",
|
| 282 |
+
"domutils": "^3.0.1"
|
| 283 |
+
},
|
| 284 |
+
"funding": {
|
| 285 |
+
"url": "https://github.com/sponsors/fb55"
|
| 286 |
+
}
|
| 287 |
+
},
|
| 288 |
+
"node_modules/chokidar": {
|
| 289 |
+
"version": "3.6.0",
|
| 290 |
+
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz",
|
| 291 |
+
"integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==",
|
| 292 |
+
"dev": true,
|
| 293 |
+
"license": "MIT",
|
| 294 |
+
"dependencies": {
|
| 295 |
+
"anymatch": "~3.1.2",
|
| 296 |
+
"braces": "~3.0.2",
|
| 297 |
+
"glob-parent": "~5.1.2",
|
| 298 |
+
"is-binary-path": "~2.1.0",
|
| 299 |
+
"is-glob": "~4.0.1",
|
| 300 |
+
"normalize-path": "~3.0.0",
|
| 301 |
+
"readdirp": "~3.6.0"
|
| 302 |
+
},
|
| 303 |
+
"engines": {
|
| 304 |
+
"node": ">= 8.10.0"
|
| 305 |
+
},
|
| 306 |
+
"funding": {
|
| 307 |
+
"url": "https://paulmillr.com/funding/"
|
| 308 |
+
},
|
| 309 |
+
"optionalDependencies": {
|
| 310 |
+
"fsevents": "~2.3.2"
|
| 311 |
+
}
|
| 312 |
+
},
|
| 313 |
+
"node_modules/combined-stream": {
|
| 314 |
+
"version": "1.0.8",
|
| 315 |
+
"resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
|
| 316 |
+
"integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
|
| 317 |
+
"license": "MIT",
|
| 318 |
+
"dependencies": {
|
| 319 |
+
"delayed-stream": "~1.0.0"
|
| 320 |
+
},
|
| 321 |
+
"engines": {
|
| 322 |
+
"node": ">= 0.8"
|
| 323 |
+
}
|
| 324 |
+
},
|
| 325 |
+
"node_modules/concat-map": {
|
| 326 |
+
"version": "0.0.1",
|
| 327 |
+
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
|
| 328 |
+
"integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==",
|
| 329 |
+
"dev": true,
|
| 330 |
+
"license": "MIT"
|
| 331 |
+
},
|
| 332 |
+
"node_modules/content-disposition": {
|
| 333 |
+
"version": "0.5.4",
|
| 334 |
+
"resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz",
|
| 335 |
+
"integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==",
|
| 336 |
+
"license": "MIT",
|
| 337 |
+
"dependencies": {
|
| 338 |
+
"safe-buffer": "5.2.1"
|
| 339 |
+
},
|
| 340 |
+
"engines": {
|
| 341 |
+
"node": ">= 0.6"
|
| 342 |
+
}
|
| 343 |
+
},
|
| 344 |
+
"node_modules/content-type": {
|
| 345 |
+
"version": "1.0.5",
|
| 346 |
+
"resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz",
|
| 347 |
+
"integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==",
|
| 348 |
+
"license": "MIT",
|
| 349 |
+
"engines": {
|
| 350 |
+
"node": ">= 0.6"
|
| 351 |
+
}
|
| 352 |
+
},
|
| 353 |
+
"node_modules/cookie": {
|
| 354 |
+
"version": "0.7.1",
|
| 355 |
+
"resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.1.tgz",
|
| 356 |
+
"integrity": "sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w==",
|
| 357 |
+
"license": "MIT",
|
| 358 |
+
"engines": {
|
| 359 |
+
"node": ">= 0.6"
|
| 360 |
+
}
|
| 361 |
+
},
|
| 362 |
+
"node_modules/cookie-signature": {
|
| 363 |
+
"version": "1.0.6",
|
| 364 |
+
"resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz",
|
| 365 |
+
"integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==",
|
| 366 |
+
"license": "MIT"
|
| 367 |
+
},
|
| 368 |
+
"node_modules/cors": {
|
| 369 |
+
"version": "2.8.5",
|
| 370 |
+
"resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz",
|
| 371 |
+
"integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==",
|
| 372 |
+
"license": "MIT",
|
| 373 |
+
"dependencies": {
|
| 374 |
+
"object-assign": "^4",
|
| 375 |
+
"vary": "^1"
|
| 376 |
+
},
|
| 377 |
+
"engines": {
|
| 378 |
+
"node": ">= 0.10"
|
| 379 |
+
}
|
| 380 |
+
},
|
| 381 |
+
"node_modules/css-select": {
|
| 382 |
+
"version": "5.2.2",
|
| 383 |
+
"resolved": "https://registry.npmjs.org/css-select/-/css-select-5.2.2.tgz",
|
| 384 |
+
"integrity": "sha512-TizTzUddG/xYLA3NXodFM0fSbNizXjOKhqiQQwvhlspadZokn1KDy0NZFS0wuEubIYAV5/c1/lAr0TaaFXEXzw==",
|
| 385 |
+
"license": "BSD-2-Clause",
|
| 386 |
+
"dependencies": {
|
| 387 |
+
"boolbase": "^1.0.0",
|
| 388 |
+
"css-what": "^6.1.0",
|
| 389 |
+
"domhandler": "^5.0.2",
|
| 390 |
+
"domutils": "^3.0.1",
|
| 391 |
+
"nth-check": "^2.0.1"
|
| 392 |
+
},
|
| 393 |
+
"funding": {
|
| 394 |
+
"url": "https://github.com/sponsors/fb55"
|
| 395 |
+
}
|
| 396 |
+
},
|
| 397 |
+
"node_modules/css-what": {
|
| 398 |
+
"version": "6.2.2",
|
| 399 |
+
"resolved": "https://registry.npmjs.org/css-what/-/css-what-6.2.2.tgz",
|
| 400 |
+
"integrity": "sha512-u/O3vwbptzhMs3L1fQE82ZSLHQQfto5gyZzwteVIEyeaY5Fc7R4dapF/BvRoSYFeqfBk4m0V1Vafq5Pjv25wvA==",
|
| 401 |
+
"license": "BSD-2-Clause",
|
| 402 |
+
"engines": {
|
| 403 |
+
"node": ">= 6"
|
| 404 |
+
},
|
| 405 |
+
"funding": {
|
| 406 |
+
"url": "https://github.com/sponsors/fb55"
|
| 407 |
+
}
|
| 408 |
+
},
|
| 409 |
+
"node_modules/debug": {
|
| 410 |
+
"version": "2.6.9",
|
| 411 |
+
"resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
|
| 412 |
+
"integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
|
| 413 |
+
"license": "MIT",
|
| 414 |
+
"dependencies": {
|
| 415 |
+
"ms": "2.0.0"
|
| 416 |
+
}
|
| 417 |
+
},
|
| 418 |
+
"node_modules/delayed-stream": {
|
| 419 |
+
"version": "1.0.0",
|
| 420 |
+
"resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
|
| 421 |
+
"integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==",
|
| 422 |
+
"license": "MIT",
|
| 423 |
+
"engines": {
|
| 424 |
+
"node": ">=0.4.0"
|
| 425 |
+
}
|
| 426 |
+
},
|
| 427 |
+
"node_modules/depd": {
|
| 428 |
+
"version": "2.0.0",
|
| 429 |
+
"resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz",
|
| 430 |
+
"integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==",
|
| 431 |
+
"license": "MIT",
|
| 432 |
+
"engines": {
|
| 433 |
+
"node": ">= 0.8"
|
| 434 |
+
}
|
| 435 |
+
},
|
| 436 |
+
"node_modules/destroy": {
|
| 437 |
+
"version": "1.2.0",
|
| 438 |
+
"resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz",
|
| 439 |
+
"integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==",
|
| 440 |
+
"license": "MIT",
|
| 441 |
+
"engines": {
|
| 442 |
+
"node": ">= 0.8",
|
| 443 |
+
"npm": "1.2.8000 || >= 1.4.16"
|
| 444 |
+
}
|
| 445 |
+
},
|
| 446 |
+
"node_modules/dom-serializer": {
|
| 447 |
+
"version": "2.0.0",
|
| 448 |
+
"resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz",
|
| 449 |
+
"integrity": "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==",
|
| 450 |
+
"license": "MIT",
|
| 451 |
+
"dependencies": {
|
| 452 |
+
"domelementtype": "^2.3.0",
|
| 453 |
+
"domhandler": "^5.0.2",
|
| 454 |
+
"entities": "^4.2.0"
|
| 455 |
+
},
|
| 456 |
+
"funding": {
|
| 457 |
+
"url": "https://github.com/cheeriojs/dom-serializer?sponsor=1"
|
| 458 |
+
}
|
| 459 |
+
},
|
| 460 |
+
"node_modules/domelementtype": {
|
| 461 |
+
"version": "2.3.0",
|
| 462 |
+
"resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz",
|
| 463 |
+
"integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==",
|
| 464 |
+
"funding": [
|
| 465 |
+
{
|
| 466 |
+
"type": "github",
|
| 467 |
+
"url": "https://github.com/sponsors/fb55"
|
| 468 |
+
}
|
| 469 |
+
],
|
| 470 |
+
"license": "BSD-2-Clause"
|
| 471 |
+
},
|
| 472 |
+
"node_modules/domhandler": {
|
| 473 |
+
"version": "5.0.3",
|
| 474 |
+
"resolved": "https://registry.npmjs.org/domhandler/-/domhandler-5.0.3.tgz",
|
| 475 |
+
"integrity": "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==",
|
| 476 |
+
"license": "BSD-2-Clause",
|
| 477 |
+
"dependencies": {
|
| 478 |
+
"domelementtype": "^2.3.0"
|
| 479 |
+
},
|
| 480 |
+
"engines": {
|
| 481 |
+
"node": ">= 4"
|
| 482 |
+
},
|
| 483 |
+
"funding": {
|
| 484 |
+
"url": "https://github.com/fb55/domhandler?sponsor=1"
|
| 485 |
+
}
|
| 486 |
+
},
|
| 487 |
+
"node_modules/domutils": {
|
| 488 |
+
"version": "3.2.2",
|
| 489 |
+
"resolved": "https://registry.npmjs.org/domutils/-/domutils-3.2.2.tgz",
|
| 490 |
+
"integrity": "sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw==",
|
| 491 |
+
"license": "BSD-2-Clause",
|
| 492 |
+
"dependencies": {
|
| 493 |
+
"dom-serializer": "^2.0.0",
|
| 494 |
+
"domelementtype": "^2.3.0",
|
| 495 |
+
"domhandler": "^5.0.3"
|
| 496 |
+
},
|
| 497 |
+
"funding": {
|
| 498 |
+
"url": "https://github.com/fb55/domutils?sponsor=1"
|
| 499 |
+
}
|
| 500 |
+
},
|
| 501 |
+
"node_modules/dotenv": {
|
| 502 |
+
"version": "16.6.1",
|
| 503 |
+
"resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.6.1.tgz",
|
| 504 |
+
"integrity": "sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow==",
|
| 505 |
+
"license": "BSD-2-Clause",
|
| 506 |
+
"engines": {
|
| 507 |
+
"node": ">=12"
|
| 508 |
+
},
|
| 509 |
+
"funding": {
|
| 510 |
+
"url": "https://dotenvx.com"
|
| 511 |
+
}
|
| 512 |
+
},
|
| 513 |
+
"node_modules/dunder-proto": {
|
| 514 |
+
"version": "1.0.1",
|
| 515 |
+
"resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz",
|
| 516 |
+
"integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==",
|
| 517 |
+
"license": "MIT",
|
| 518 |
+
"dependencies": {
|
| 519 |
+
"call-bind-apply-helpers": "^1.0.1",
|
| 520 |
+
"es-errors": "^1.3.0",
|
| 521 |
+
"gopd": "^1.2.0"
|
| 522 |
+
},
|
| 523 |
+
"engines": {
|
| 524 |
+
"node": ">= 0.4"
|
| 525 |
+
}
|
| 526 |
+
},
|
| 527 |
+
"node_modules/ecdsa-sig-formatter": {
|
| 528 |
+
"version": "1.0.11",
|
| 529 |
+
"resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz",
|
| 530 |
+
"integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==",
|
| 531 |
+
"license": "Apache-2.0",
|
| 532 |
+
"dependencies": {
|
| 533 |
+
"safe-buffer": "^5.0.1"
|
| 534 |
+
}
|
| 535 |
+
},
|
| 536 |
+
"node_modules/ee-first": {
|
| 537 |
+
"version": "1.1.1",
|
| 538 |
+
"resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
|
| 539 |
+
"integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==",
|
| 540 |
+
"license": "MIT"
|
| 541 |
+
},
|
| 542 |
+
"node_modules/encodeurl": {
|
| 543 |
+
"version": "2.0.0",
|
| 544 |
+
"resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz",
|
| 545 |
+
"integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==",
|
| 546 |
+
"license": "MIT",
|
| 547 |
+
"engines": {
|
| 548 |
+
"node": ">= 0.8"
|
| 549 |
+
}
|
| 550 |
+
},
|
| 551 |
+
"node_modules/encoding-sniffer": {
|
| 552 |
+
"version": "0.2.1",
|
| 553 |
+
"resolved": "https://registry.npmjs.org/encoding-sniffer/-/encoding-sniffer-0.2.1.tgz",
|
| 554 |
+
"integrity": "sha512-5gvq20T6vfpekVtqrYQsSCFZ1wEg5+wW0/QaZMWkFr6BqD3NfKs0rLCx4rrVlSWJeZb5NBJgVLswK/w2MWU+Gw==",
|
| 555 |
+
"license": "MIT",
|
| 556 |
+
"dependencies": {
|
| 557 |
+
"iconv-lite": "^0.6.3",
|
| 558 |
+
"whatwg-encoding": "^3.1.1"
|
| 559 |
+
},
|
| 560 |
+
"funding": {
|
| 561 |
+
"url": "https://github.com/fb55/encoding-sniffer?sponsor=1"
|
| 562 |
+
}
|
| 563 |
+
},
|
| 564 |
+
"node_modules/entities": {
|
| 565 |
+
"version": "4.5.0",
|
| 566 |
+
"resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz",
|
| 567 |
+
"integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==",
|
| 568 |
+
"license": "BSD-2-Clause",
|
| 569 |
+
"engines": {
|
| 570 |
+
"node": ">=0.12"
|
| 571 |
+
},
|
| 572 |
+
"funding": {
|
| 573 |
+
"url": "https://github.com/fb55/entities?sponsor=1"
|
| 574 |
+
}
|
| 575 |
+
},
|
| 576 |
+
"node_modules/es-define-property": {
|
| 577 |
+
"version": "1.0.1",
|
| 578 |
+
"resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz",
|
| 579 |
+
"integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==",
|
| 580 |
+
"license": "MIT",
|
| 581 |
+
"engines": {
|
| 582 |
+
"node": ">= 0.4"
|
| 583 |
+
}
|
| 584 |
+
},
|
| 585 |
+
"node_modules/es-errors": {
|
| 586 |
+
"version": "1.3.0",
|
| 587 |
+
"resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz",
|
| 588 |
+
"integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==",
|
| 589 |
+
"license": "MIT",
|
| 590 |
+
"engines": {
|
| 591 |
+
"node": ">= 0.4"
|
| 592 |
+
}
|
| 593 |
+
},
|
| 594 |
+
"node_modules/es-object-atoms": {
|
| 595 |
+
"version": "1.1.1",
|
| 596 |
+
"resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz",
|
| 597 |
+
"integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==",
|
| 598 |
+
"license": "MIT",
|
| 599 |
+
"dependencies": {
|
| 600 |
+
"es-errors": "^1.3.0"
|
| 601 |
+
},
|
| 602 |
+
"engines": {
|
| 603 |
+
"node": ">= 0.4"
|
| 604 |
+
}
|
| 605 |
+
},
|
| 606 |
+
"node_modules/es-set-tostringtag": {
|
| 607 |
+
"version": "2.1.0",
|
| 608 |
+
"resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz",
|
| 609 |
+
"integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==",
|
| 610 |
+
"license": "MIT",
|
| 611 |
+
"dependencies": {
|
| 612 |
+
"es-errors": "^1.3.0",
|
| 613 |
+
"get-intrinsic": "^1.2.6",
|
| 614 |
+
"has-tostringtag": "^1.0.2",
|
| 615 |
+
"hasown": "^2.0.2"
|
| 616 |
+
},
|
| 617 |
+
"engines": {
|
| 618 |
+
"node": ">= 0.4"
|
| 619 |
+
}
|
| 620 |
+
},
|
| 621 |
+
"node_modules/escape-html": {
|
| 622 |
+
"version": "1.0.3",
|
| 623 |
+
"resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz",
|
| 624 |
+
"integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==",
|
| 625 |
+
"license": "MIT"
|
| 626 |
+
},
|
| 627 |
+
"node_modules/etag": {
|
| 628 |
+
"version": "1.8.1",
|
| 629 |
+
"resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz",
|
| 630 |
+
"integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==",
|
| 631 |
+
"license": "MIT",
|
| 632 |
+
"engines": {
|
| 633 |
+
"node": ">= 0.6"
|
| 634 |
+
}
|
| 635 |
+
},
|
| 636 |
+
"node_modules/express": {
|
| 637 |
+
"version": "4.21.2",
|
| 638 |
+
"resolved": "https://registry.npmjs.org/express/-/express-4.21.2.tgz",
|
| 639 |
+
"integrity": "sha512-28HqgMZAmih1Czt9ny7qr6ek2qddF4FclbMzwhCREB6OFfH+rXAnuNCwo1/wFvrtbgsQDb4kSbX9de9lFbrXnA==",
|
| 640 |
+
"license": "MIT",
|
| 641 |
+
"dependencies": {
|
| 642 |
+
"accepts": "~1.3.8",
|
| 643 |
+
"array-flatten": "1.1.1",
|
| 644 |
+
"body-parser": "1.20.3",
|
| 645 |
+
"content-disposition": "0.5.4",
|
| 646 |
+
"content-type": "~1.0.4",
|
| 647 |
+
"cookie": "0.7.1",
|
| 648 |
+
"cookie-signature": "1.0.6",
|
| 649 |
+
"debug": "2.6.9",
|
| 650 |
+
"depd": "2.0.0",
|
| 651 |
+
"encodeurl": "~2.0.0",
|
| 652 |
+
"escape-html": "~1.0.3",
|
| 653 |
+
"etag": "~1.8.1",
|
| 654 |
+
"finalhandler": "1.3.1",
|
| 655 |
+
"fresh": "0.5.2",
|
| 656 |
+
"http-errors": "2.0.0",
|
| 657 |
+
"merge-descriptors": "1.0.3",
|
| 658 |
+
"methods": "~1.1.2",
|
| 659 |
+
"on-finished": "2.4.1",
|
| 660 |
+
"parseurl": "~1.3.3",
|
| 661 |
+
"path-to-regexp": "0.1.12",
|
| 662 |
+
"proxy-addr": "~2.0.7",
|
| 663 |
+
"qs": "6.13.0",
|
| 664 |
+
"range-parser": "~1.2.1",
|
| 665 |
+
"safe-buffer": "5.2.1",
|
| 666 |
+
"send": "0.19.0",
|
| 667 |
+
"serve-static": "1.16.2",
|
| 668 |
+
"setprototypeof": "1.2.0",
|
| 669 |
+
"statuses": "2.0.1",
|
| 670 |
+
"type-is": "~1.6.18",
|
| 671 |
+
"utils-merge": "1.0.1",
|
| 672 |
+
"vary": "~1.1.2"
|
| 673 |
+
},
|
| 674 |
+
"engines": {
|
| 675 |
+
"node": ">= 0.10.0"
|
| 676 |
+
},
|
| 677 |
+
"funding": {
|
| 678 |
+
"type": "opencollective",
|
| 679 |
+
"url": "https://opencollective.com/express"
|
| 680 |
+
}
|
| 681 |
+
},
|
| 682 |
+
"node_modules/express-rate-limit": {
|
| 683 |
+
"version": "7.5.1",
|
| 684 |
+
"resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-7.5.1.tgz",
|
| 685 |
+
"integrity": "sha512-7iN8iPMDzOMHPUYllBEsQdWVB6fPDMPqwjBaFrgr4Jgr/+okjvzAy+UHlYYL/Vs0OsOrMkwS6PJDkFlJwoxUnw==",
|
| 686 |
+
"license": "MIT",
|
| 687 |
+
"engines": {
|
| 688 |
+
"node": ">= 16"
|
| 689 |
+
},
|
| 690 |
+
"funding": {
|
| 691 |
+
"url": "https://github.com/sponsors/express-rate-limit"
|
| 692 |
+
},
|
| 693 |
+
"peerDependencies": {
|
| 694 |
+
"express": ">= 4.11"
|
| 695 |
+
}
|
| 696 |
+
},
|
| 697 |
+
"node_modules/express-validator": {
|
| 698 |
+
"version": "7.2.1",
|
| 699 |
+
"resolved": "https://registry.npmjs.org/express-validator/-/express-validator-7.2.1.tgz",
|
| 700 |
+
"integrity": "sha512-CjNE6aakfpuwGaHQZ3m8ltCG2Qvivd7RHtVMS/6nVxOM7xVGqr4bhflsm4+N5FP5zI7Zxp+Hae+9RE+o8e3ZOQ==",
|
| 701 |
+
"license": "MIT",
|
| 702 |
+
"dependencies": {
|
| 703 |
+
"lodash": "^4.17.21",
|
| 704 |
+
"validator": "~13.12.0"
|
| 705 |
+
},
|
| 706 |
+
"engines": {
|
| 707 |
+
"node": ">= 8.0.0"
|
| 708 |
+
}
|
| 709 |
+
},
|
| 710 |
+
"node_modules/fill-range": {
|
| 711 |
+
"version": "7.1.1",
|
| 712 |
+
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz",
|
| 713 |
+
"integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==",
|
| 714 |
+
"dev": true,
|
| 715 |
+
"license": "MIT",
|
| 716 |
+
"dependencies": {
|
| 717 |
+
"to-regex-range": "^5.0.1"
|
| 718 |
+
},
|
| 719 |
+
"engines": {
|
| 720 |
+
"node": ">=8"
|
| 721 |
+
}
|
| 722 |
+
},
|
| 723 |
+
"node_modules/finalhandler": {
|
| 724 |
+
"version": "1.3.1",
|
| 725 |
+
"resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.1.tgz",
|
| 726 |
+
"integrity": "sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ==",
|
| 727 |
+
"license": "MIT",
|
| 728 |
+
"dependencies": {
|
| 729 |
+
"debug": "2.6.9",
|
| 730 |
+
"encodeurl": "~2.0.0",
|
| 731 |
+
"escape-html": "~1.0.3",
|
| 732 |
+
"on-finished": "2.4.1",
|
| 733 |
+
"parseurl": "~1.3.3",
|
| 734 |
+
"statuses": "2.0.1",
|
| 735 |
+
"unpipe": "~1.0.0"
|
| 736 |
+
},
|
| 737 |
+
"engines": {
|
| 738 |
+
"node": ">= 0.8"
|
| 739 |
+
}
|
| 740 |
+
},
|
| 741 |
+
"node_modules/follow-redirects": {
|
| 742 |
+
"version": "1.15.9",
|
| 743 |
+
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.9.tgz",
|
| 744 |
+
"integrity": "sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==",
|
| 745 |
+
"funding": [
|
| 746 |
+
{
|
| 747 |
+
"type": "individual",
|
| 748 |
+
"url": "https://github.com/sponsors/RubenVerborgh"
|
| 749 |
+
}
|
| 750 |
+
],
|
| 751 |
+
"license": "MIT",
|
| 752 |
+
"engines": {
|
| 753 |
+
"node": ">=4.0"
|
| 754 |
+
},
|
| 755 |
+
"peerDependenciesMeta": {
|
| 756 |
+
"debug": {
|
| 757 |
+
"optional": true
|
| 758 |
+
}
|
| 759 |
+
}
|
| 760 |
+
},
|
| 761 |
+
"node_modules/form-data": {
|
| 762 |
+
"version": "4.0.4",
|
| 763 |
+
"resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.4.tgz",
|
| 764 |
+
"integrity": "sha512-KrGhL9Q4zjj0kiUt5OO4Mr/A/jlI2jDYs5eHBpYHPcBEVSiipAvn2Ko2HnPe20rmcuuvMHNdZFp+4IlGTMF0Ow==",
|
| 765 |
+
"license": "MIT",
|
| 766 |
+
"dependencies": {
|
| 767 |
+
"asynckit": "^0.4.0",
|
| 768 |
+
"combined-stream": "^1.0.8",
|
| 769 |
+
"es-set-tostringtag": "^2.1.0",
|
| 770 |
+
"hasown": "^2.0.2",
|
| 771 |
+
"mime-types": "^2.1.12"
|
| 772 |
+
},
|
| 773 |
+
"engines": {
|
| 774 |
+
"node": ">= 6"
|
| 775 |
+
}
|
| 776 |
+
},
|
| 777 |
+
"node_modules/forwarded": {
|
| 778 |
+
"version": "0.2.0",
|
| 779 |
+
"resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz",
|
| 780 |
+
"integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==",
|
| 781 |
+
"license": "MIT",
|
| 782 |
+
"engines": {
|
| 783 |
+
"node": ">= 0.6"
|
| 784 |
+
}
|
| 785 |
+
},
|
| 786 |
+
"node_modules/fresh": {
|
| 787 |
+
"version": "0.5.2",
|
| 788 |
+
"resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz",
|
| 789 |
+
"integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==",
|
| 790 |
+
"license": "MIT",
|
| 791 |
+
"engines": {
|
| 792 |
+
"node": ">= 0.6"
|
| 793 |
+
}
|
| 794 |
+
},
|
| 795 |
+
"node_modules/fsevents": {
|
| 796 |
+
"version": "2.3.3",
|
| 797 |
+
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
|
| 798 |
+
"integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
|
| 799 |
+
"dev": true,
|
| 800 |
+
"hasInstallScript": true,
|
| 801 |
+
"license": "MIT",
|
| 802 |
+
"optional": true,
|
| 803 |
+
"os": [
|
| 804 |
+
"darwin"
|
| 805 |
+
],
|
| 806 |
+
"engines": {
|
| 807 |
+
"node": "^8.16.0 || ^10.6.0 || >=11.0.0"
|
| 808 |
+
}
|
| 809 |
+
},
|
| 810 |
+
"node_modules/function-bind": {
|
| 811 |
+
"version": "1.1.2",
|
| 812 |
+
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
|
| 813 |
+
"integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==",
|
| 814 |
+
"license": "MIT",
|
| 815 |
+
"funding": {
|
| 816 |
+
"url": "https://github.com/sponsors/ljharb"
|
| 817 |
+
}
|
| 818 |
+
},
|
| 819 |
+
"node_modules/get-intrinsic": {
|
| 820 |
+
"version": "1.3.0",
|
| 821 |
+
"resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz",
|
| 822 |
+
"integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==",
|
| 823 |
+
"license": "MIT",
|
| 824 |
+
"dependencies": {
|
| 825 |
+
"call-bind-apply-helpers": "^1.0.2",
|
| 826 |
+
"es-define-property": "^1.0.1",
|
| 827 |
+
"es-errors": "^1.3.0",
|
| 828 |
+
"es-object-atoms": "^1.1.1",
|
| 829 |
+
"function-bind": "^1.1.2",
|
| 830 |
+
"get-proto": "^1.0.1",
|
| 831 |
+
"gopd": "^1.2.0",
|
| 832 |
+
"has-symbols": "^1.1.0",
|
| 833 |
+
"hasown": "^2.0.2",
|
| 834 |
+
"math-intrinsics": "^1.1.0"
|
| 835 |
+
},
|
| 836 |
+
"engines": {
|
| 837 |
+
"node": ">= 0.4"
|
| 838 |
+
},
|
| 839 |
+
"funding": {
|
| 840 |
+
"url": "https://github.com/sponsors/ljharb"
|
| 841 |
+
}
|
| 842 |
+
},
|
| 843 |
+
"node_modules/get-proto": {
|
| 844 |
+
"version": "1.0.1",
|
| 845 |
+
"resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz",
|
| 846 |
+
"integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==",
|
| 847 |
+
"license": "MIT",
|
| 848 |
+
"dependencies": {
|
| 849 |
+
"dunder-proto": "^1.0.1",
|
| 850 |
+
"es-object-atoms": "^1.0.0"
|
| 851 |
+
},
|
| 852 |
+
"engines": {
|
| 853 |
+
"node": ">= 0.4"
|
| 854 |
+
}
|
| 855 |
+
},
|
| 856 |
+
"node_modules/glob-parent": {
|
| 857 |
+
"version": "5.1.2",
|
| 858 |
+
"resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz",
|
| 859 |
+
"integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==",
|
| 860 |
+
"dev": true,
|
| 861 |
+
"license": "ISC",
|
| 862 |
+
"dependencies": {
|
| 863 |
+
"is-glob": "^4.0.1"
|
| 864 |
+
},
|
| 865 |
+
"engines": {
|
| 866 |
+
"node": ">= 6"
|
| 867 |
+
}
|
| 868 |
+
},
|
| 869 |
+
"node_modules/gopd": {
|
| 870 |
+
"version": "1.2.0",
|
| 871 |
+
"resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz",
|
| 872 |
+
"integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==",
|
| 873 |
+
"license": "MIT",
|
| 874 |
+
"engines": {
|
| 875 |
+
"node": ">= 0.4"
|
| 876 |
+
},
|
| 877 |
+
"funding": {
|
| 878 |
+
"url": "https://github.com/sponsors/ljharb"
|
| 879 |
+
}
|
| 880 |
+
},
|
| 881 |
+
"node_modules/has-flag": {
|
| 882 |
+
"version": "3.0.0",
|
| 883 |
+
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz",
|
| 884 |
+
"integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==",
|
| 885 |
+
"dev": true,
|
| 886 |
+
"license": "MIT",
|
| 887 |
+
"engines": {
|
| 888 |
+
"node": ">=4"
|
| 889 |
+
}
|
| 890 |
+
},
|
| 891 |
+
"node_modules/has-symbols": {
|
| 892 |
+
"version": "1.1.0",
|
| 893 |
+
"resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz",
|
| 894 |
+
"integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==",
|
| 895 |
+
"license": "MIT",
|
| 896 |
+
"engines": {
|
| 897 |
+
"node": ">= 0.4"
|
| 898 |
+
},
|
| 899 |
+
"funding": {
|
| 900 |
+
"url": "https://github.com/sponsors/ljharb"
|
| 901 |
+
}
|
| 902 |
+
},
|
| 903 |
+
"node_modules/has-tostringtag": {
|
| 904 |
+
"version": "1.0.2",
|
| 905 |
+
"resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz",
|
| 906 |
+
"integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==",
|
| 907 |
+
"license": "MIT",
|
| 908 |
+
"dependencies": {
|
| 909 |
+
"has-symbols": "^1.0.3"
|
| 910 |
+
},
|
| 911 |
+
"engines": {
|
| 912 |
+
"node": ">= 0.4"
|
| 913 |
+
},
|
| 914 |
+
"funding": {
|
| 915 |
+
"url": "https://github.com/sponsors/ljharb"
|
| 916 |
+
}
|
| 917 |
+
},
|
| 918 |
+
"node_modules/hasown": {
|
| 919 |
+
"version": "2.0.2",
|
| 920 |
+
"resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz",
|
| 921 |
+
"integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==",
|
| 922 |
+
"license": "MIT",
|
| 923 |
+
"dependencies": {
|
| 924 |
+
"function-bind": "^1.1.2"
|
| 925 |
+
},
|
| 926 |
+
"engines": {
|
| 927 |
+
"node": ">= 0.4"
|
| 928 |
+
}
|
| 929 |
+
},
|
| 930 |
+
"node_modules/htmlparser2": {
|
| 931 |
+
"version": "10.0.0",
|
| 932 |
+
"resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-10.0.0.tgz",
|
| 933 |
+
"integrity": "sha512-TwAZM+zE5Tq3lrEHvOlvwgj1XLWQCtaaibSN11Q+gGBAS7Y1uZSWwXXRe4iF6OXnaq1riyQAPFOBtYc77Mxq0g==",
|
| 934 |
+
"funding": [
|
| 935 |
+
"https://github.com/fb55/htmlparser2?sponsor=1",
|
| 936 |
+
{
|
| 937 |
+
"type": "github",
|
| 938 |
+
"url": "https://github.com/sponsors/fb55"
|
| 939 |
+
}
|
| 940 |
+
],
|
| 941 |
+
"license": "MIT",
|
| 942 |
+
"dependencies": {
|
| 943 |
+
"domelementtype": "^2.3.0",
|
| 944 |
+
"domhandler": "^5.0.3",
|
| 945 |
+
"domutils": "^3.2.1",
|
| 946 |
+
"entities": "^6.0.0"
|
| 947 |
+
}
|
| 948 |
+
},
|
| 949 |
+
"node_modules/htmlparser2/node_modules/entities": {
|
| 950 |
+
"version": "6.0.1",
|
| 951 |
+
"resolved": "https://registry.npmjs.org/entities/-/entities-6.0.1.tgz",
|
| 952 |
+
"integrity": "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==",
|
| 953 |
+
"license": "BSD-2-Clause",
|
| 954 |
+
"engines": {
|
| 955 |
+
"node": ">=0.12"
|
| 956 |
+
},
|
| 957 |
+
"funding": {
|
| 958 |
+
"url": "https://github.com/fb55/entities?sponsor=1"
|
| 959 |
+
}
|
| 960 |
+
},
|
| 961 |
+
"node_modules/http-errors": {
|
| 962 |
+
"version": "2.0.0",
|
| 963 |
+
"resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz",
|
| 964 |
+
"integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==",
|
| 965 |
+
"license": "MIT",
|
| 966 |
+
"dependencies": {
|
| 967 |
+
"depd": "2.0.0",
|
| 968 |
+
"inherits": "2.0.4",
|
| 969 |
+
"setprototypeof": "1.2.0",
|
| 970 |
+
"statuses": "2.0.1",
|
| 971 |
+
"toidentifier": "1.0.1"
|
| 972 |
+
},
|
| 973 |
+
"engines": {
|
| 974 |
+
"node": ">= 0.8"
|
| 975 |
+
}
|
| 976 |
+
},
|
| 977 |
+
"node_modules/iconv-lite": {
|
| 978 |
+
"version": "0.6.3",
|
| 979 |
+
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz",
|
| 980 |
+
"integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==",
|
| 981 |
+
"license": "MIT",
|
| 982 |
+
"dependencies": {
|
| 983 |
+
"safer-buffer": ">= 2.1.2 < 3.0.0"
|
| 984 |
+
},
|
| 985 |
+
"engines": {
|
| 986 |
+
"node": ">=0.10.0"
|
| 987 |
+
}
|
| 988 |
+
},
|
| 989 |
+
"node_modules/ignore-by-default": {
|
| 990 |
+
"version": "1.0.1",
|
| 991 |
+
"resolved": "https://registry.npmjs.org/ignore-by-default/-/ignore-by-default-1.0.1.tgz",
|
| 992 |
+
"integrity": "sha512-Ius2VYcGNk7T90CppJqcIkS5ooHUZyIQK+ClZfMfMNFEF9VSE73Fq+906u/CWu92x4gzZMWOwfFYckPObzdEbA==",
|
| 993 |
+
"dev": true,
|
| 994 |
+
"license": "ISC"
|
| 995 |
+
},
|
| 996 |
+
"node_modules/inherits": {
|
| 997 |
+
"version": "2.0.4",
|
| 998 |
+
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
|
| 999 |
+
"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
|
| 1000 |
+
"license": "ISC"
|
| 1001 |
+
},
|
| 1002 |
+
"node_modules/ipaddr.js": {
|
| 1003 |
+
"version": "1.9.1",
|
| 1004 |
+
"resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz",
|
| 1005 |
+
"integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==",
|
| 1006 |
+
"license": "MIT",
|
| 1007 |
+
"engines": {
|
| 1008 |
+
"node": ">= 0.10"
|
| 1009 |
+
}
|
| 1010 |
+
},
|
| 1011 |
+
"node_modules/is-binary-path": {
|
| 1012 |
+
"version": "2.1.0",
|
| 1013 |
+
"resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz",
|
| 1014 |
+
"integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==",
|
| 1015 |
+
"dev": true,
|
| 1016 |
+
"license": "MIT",
|
| 1017 |
+
"dependencies": {
|
| 1018 |
+
"binary-extensions": "^2.0.0"
|
| 1019 |
+
},
|
| 1020 |
+
"engines": {
|
| 1021 |
+
"node": ">=8"
|
| 1022 |
+
}
|
| 1023 |
+
},
|
| 1024 |
+
"node_modules/is-extglob": {
|
| 1025 |
+
"version": "2.1.1",
|
| 1026 |
+
"resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
|
| 1027 |
+
"integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==",
|
| 1028 |
+
"dev": true,
|
| 1029 |
+
"license": "MIT",
|
| 1030 |
+
"engines": {
|
| 1031 |
+
"node": ">=0.10.0"
|
| 1032 |
+
}
|
| 1033 |
+
},
|
| 1034 |
+
"node_modules/is-glob": {
|
| 1035 |
+
"version": "4.0.3",
|
| 1036 |
+
"resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz",
|
| 1037 |
+
"integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==",
|
| 1038 |
+
"dev": true,
|
| 1039 |
+
"license": "MIT",
|
| 1040 |
+
"dependencies": {
|
| 1041 |
+
"is-extglob": "^2.1.1"
|
| 1042 |
+
},
|
| 1043 |
+
"engines": {
|
| 1044 |
+
"node": ">=0.10.0"
|
| 1045 |
+
}
|
| 1046 |
+
},
|
| 1047 |
+
"node_modules/is-number": {
|
| 1048 |
+
"version": "7.0.0",
|
| 1049 |
+
"resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz",
|
| 1050 |
+
"integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==",
|
| 1051 |
+
"dev": true,
|
| 1052 |
+
"license": "MIT",
|
| 1053 |
+
"engines": {
|
| 1054 |
+
"node": ">=0.12.0"
|
| 1055 |
+
}
|
| 1056 |
+
},
|
| 1057 |
+
"node_modules/jsonwebtoken": {
|
| 1058 |
+
"version": "9.0.2",
|
| 1059 |
+
"resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.2.tgz",
|
| 1060 |
+
"integrity": "sha512-PRp66vJ865SSqOlgqS8hujT5U4AOgMfhrwYIuIhfKaoSCZcirrmASQr8CX7cUg+RMih+hgznrjp99o+W4pJLHQ==",
|
| 1061 |
+
"license": "MIT",
|
| 1062 |
+
"dependencies": {
|
| 1063 |
+
"jws": "^3.2.2",
|
| 1064 |
+
"lodash.includes": "^4.3.0",
|
| 1065 |
+
"lodash.isboolean": "^3.0.3",
|
| 1066 |
+
"lodash.isinteger": "^4.0.4",
|
| 1067 |
+
"lodash.isnumber": "^3.0.3",
|
| 1068 |
+
"lodash.isplainobject": "^4.0.6",
|
| 1069 |
+
"lodash.isstring": "^4.0.1",
|
| 1070 |
+
"lodash.once": "^4.0.0",
|
| 1071 |
+
"ms": "^2.1.1",
|
| 1072 |
+
"semver": "^7.5.4"
|
| 1073 |
+
},
|
| 1074 |
+
"engines": {
|
| 1075 |
+
"node": ">=12",
|
| 1076 |
+
"npm": ">=6"
|
| 1077 |
+
}
|
| 1078 |
+
},
|
| 1079 |
+
"node_modules/jsonwebtoken/node_modules/ms": {
|
| 1080 |
+
"version": "2.1.3",
|
| 1081 |
+
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
|
| 1082 |
+
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
|
| 1083 |
+
"license": "MIT"
|
| 1084 |
+
},
|
| 1085 |
+
"node_modules/jwa": {
|
| 1086 |
+
"version": "1.4.2",
|
| 1087 |
+
"resolved": "https://registry.npmjs.org/jwa/-/jwa-1.4.2.tgz",
|
| 1088 |
+
"integrity": "sha512-eeH5JO+21J78qMvTIDdBXidBd6nG2kZjg5Ohz/1fpa28Z4CcsWUzJ1ZZyFq/3z3N17aZy+ZuBoHljASbL1WfOw==",
|
| 1089 |
+
"license": "MIT",
|
| 1090 |
+
"dependencies": {
|
| 1091 |
+
"buffer-equal-constant-time": "^1.0.1",
|
| 1092 |
+
"ecdsa-sig-formatter": "1.0.11",
|
| 1093 |
+
"safe-buffer": "^5.0.1"
|
| 1094 |
+
}
|
| 1095 |
+
},
|
| 1096 |
+
"node_modules/jws": {
|
| 1097 |
+
"version": "3.2.2",
|
| 1098 |
+
"resolved": "https://registry.npmjs.org/jws/-/jws-3.2.2.tgz",
|
| 1099 |
+
"integrity": "sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==",
|
| 1100 |
+
"license": "MIT",
|
| 1101 |
+
"dependencies": {
|
| 1102 |
+
"jwa": "^1.4.1",
|
| 1103 |
+
"safe-buffer": "^5.0.1"
|
| 1104 |
+
}
|
| 1105 |
+
},
|
| 1106 |
+
"node_modules/kareem": {
|
| 1107 |
+
"version": "2.6.3",
|
| 1108 |
+
"resolved": "https://registry.npmjs.org/kareem/-/kareem-2.6.3.tgz",
|
| 1109 |
+
"integrity": "sha512-C3iHfuGUXK2u8/ipq9LfjFfXFxAZMQJJq7vLS45r3D9Y2xQ/m4S8zaR4zMLFWh9AsNPXmcFfUDhTEO8UIC/V6Q==",
|
| 1110 |
+
"license": "Apache-2.0",
|
| 1111 |
+
"engines": {
|
| 1112 |
+
"node": ">=12.0.0"
|
| 1113 |
+
}
|
| 1114 |
+
},
|
| 1115 |
+
"node_modules/lodash": {
|
| 1116 |
+
"version": "4.17.21",
|
| 1117 |
+
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
|
| 1118 |
+
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==",
|
| 1119 |
+
"license": "MIT"
|
| 1120 |
+
},
|
| 1121 |
+
"node_modules/lodash.includes": {
|
| 1122 |
+
"version": "4.3.0",
|
| 1123 |
+
"resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz",
|
| 1124 |
+
"integrity": "sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w==",
|
| 1125 |
+
"license": "MIT"
|
| 1126 |
+
},
|
| 1127 |
+
"node_modules/lodash.isboolean": {
|
| 1128 |
+
"version": "3.0.3",
|
| 1129 |
+
"resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz",
|
| 1130 |
+
"integrity": "sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==",
|
| 1131 |
+
"license": "MIT"
|
| 1132 |
+
},
|
| 1133 |
+
"node_modules/lodash.isinteger": {
|
| 1134 |
+
"version": "4.0.4",
|
| 1135 |
+
"resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz",
|
| 1136 |
+
"integrity": "sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA==",
|
| 1137 |
+
"license": "MIT"
|
| 1138 |
+
},
|
| 1139 |
+
"node_modules/lodash.isnumber": {
|
| 1140 |
+
"version": "3.0.3",
|
| 1141 |
+
"resolved": "https://registry.npmjs.org/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz",
|
| 1142 |
+
"integrity": "sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw==",
|
| 1143 |
+
"license": "MIT"
|
| 1144 |
+
},
|
| 1145 |
+
"node_modules/lodash.isplainobject": {
|
| 1146 |
+
"version": "4.0.6",
|
| 1147 |
+
"resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz",
|
| 1148 |
+
"integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==",
|
| 1149 |
+
"license": "MIT"
|
| 1150 |
+
},
|
| 1151 |
+
"node_modules/lodash.isstring": {
|
| 1152 |
+
"version": "4.0.1",
|
| 1153 |
+
"resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz",
|
| 1154 |
+
"integrity": "sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==",
|
| 1155 |
+
"license": "MIT"
|
| 1156 |
+
},
|
| 1157 |
+
"node_modules/lodash.once": {
|
| 1158 |
+
"version": "4.1.1",
|
| 1159 |
+
"resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz",
|
| 1160 |
+
"integrity": "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==",
|
| 1161 |
+
"license": "MIT"
|
| 1162 |
+
},
|
| 1163 |
+
"node_modules/math-intrinsics": {
|
| 1164 |
+
"version": "1.1.0",
|
| 1165 |
+
"resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
|
| 1166 |
+
"integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==",
|
| 1167 |
+
"license": "MIT",
|
| 1168 |
+
"engines": {
|
| 1169 |
+
"node": ">= 0.4"
|
| 1170 |
+
}
|
| 1171 |
+
},
|
| 1172 |
+
"node_modules/media-typer": {
|
| 1173 |
+
"version": "0.3.0",
|
| 1174 |
+
"resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz",
|
| 1175 |
+
"integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==",
|
| 1176 |
+
"license": "MIT",
|
| 1177 |
+
"engines": {
|
| 1178 |
+
"node": ">= 0.6"
|
| 1179 |
+
}
|
| 1180 |
+
},
|
| 1181 |
+
"node_modules/memory-pager": {
|
| 1182 |
+
"version": "1.5.0",
|
| 1183 |
+
"resolved": "https://registry.npmjs.org/memory-pager/-/memory-pager-1.5.0.tgz",
|
| 1184 |
+
"integrity": "sha512-ZS4Bp4r/Zoeq6+NLJpP+0Zzm0pR8whtGPf1XExKLJBAczGMnSi3It14OiNCStjQjM6NU1okjQGSxgEZN8eBYKg==",
|
| 1185 |
+
"license": "MIT"
|
| 1186 |
+
},
|
| 1187 |
+
"node_modules/merge-descriptors": {
|
| 1188 |
+
"version": "1.0.3",
|
| 1189 |
+
"resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz",
|
| 1190 |
+
"integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==",
|
| 1191 |
+
"license": "MIT",
|
| 1192 |
+
"funding": {
|
| 1193 |
+
"url": "https://github.com/sponsors/sindresorhus"
|
| 1194 |
+
}
|
| 1195 |
+
},
|
| 1196 |
+
"node_modules/methods": {
|
| 1197 |
+
"version": "1.1.2",
|
| 1198 |
+
"resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz",
|
| 1199 |
+
"integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==",
|
| 1200 |
+
"license": "MIT",
|
| 1201 |
+
"engines": {
|
| 1202 |
+
"node": ">= 0.6"
|
| 1203 |
+
}
|
| 1204 |
+
},
|
| 1205 |
+
"node_modules/mime": {
|
| 1206 |
+
"version": "1.6.0",
|
| 1207 |
+
"resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz",
|
| 1208 |
+
"integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==",
|
| 1209 |
+
"license": "MIT",
|
| 1210 |
+
"bin": {
|
| 1211 |
+
"mime": "cli.js"
|
| 1212 |
+
},
|
| 1213 |
+
"engines": {
|
| 1214 |
+
"node": ">=4"
|
| 1215 |
+
}
|
| 1216 |
+
},
|
| 1217 |
+
"node_modules/mime-db": {
|
| 1218 |
+
"version": "1.52.0",
|
| 1219 |
+
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
|
| 1220 |
+
"integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
|
| 1221 |
+
"license": "MIT",
|
| 1222 |
+
"engines": {
|
| 1223 |
+
"node": ">= 0.6"
|
| 1224 |
+
}
|
| 1225 |
+
},
|
| 1226 |
+
"node_modules/mime-types": {
|
| 1227 |
+
"version": "2.1.35",
|
| 1228 |
+
"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
|
| 1229 |
+
"integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
|
| 1230 |
+
"license": "MIT",
|
| 1231 |
+
"dependencies": {
|
| 1232 |
+
"mime-db": "1.52.0"
|
| 1233 |
+
},
|
| 1234 |
+
"engines": {
|
| 1235 |
+
"node": ">= 0.6"
|
| 1236 |
+
}
|
| 1237 |
+
},
|
| 1238 |
+
"node_modules/minimatch": {
|
| 1239 |
+
"version": "3.1.2",
|
| 1240 |
+
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
|
| 1241 |
+
"integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
|
| 1242 |
+
"dev": true,
|
| 1243 |
+
"license": "ISC",
|
| 1244 |
+
"dependencies": {
|
| 1245 |
+
"brace-expansion": "^1.1.7"
|
| 1246 |
+
},
|
| 1247 |
+
"engines": {
|
| 1248 |
+
"node": "*"
|
| 1249 |
+
}
|
| 1250 |
+
},
|
| 1251 |
+
"node_modules/mongodb": {
|
| 1252 |
+
"version": "6.17.0",
|
| 1253 |
+
"resolved": "https://registry.npmjs.org/mongodb/-/mongodb-6.17.0.tgz",
|
| 1254 |
+
"integrity": "sha512-neerUzg/8U26cgruLysKEjJvoNSXhyID3RvzvdcpsIi2COYM3FS3o9nlH7fxFtefTb942dX3W9i37oPfCVj4wA==",
|
| 1255 |
+
"license": "Apache-2.0",
|
| 1256 |
+
"dependencies": {
|
| 1257 |
+
"@mongodb-js/saslprep": "^1.1.9",
|
| 1258 |
+
"bson": "^6.10.4",
|
| 1259 |
+
"mongodb-connection-string-url": "^3.0.0"
|
| 1260 |
+
},
|
| 1261 |
+
"engines": {
|
| 1262 |
+
"node": ">=16.20.1"
|
| 1263 |
+
},
|
| 1264 |
+
"peerDependencies": {
|
| 1265 |
+
"@aws-sdk/credential-providers": "^3.188.0",
|
| 1266 |
+
"@mongodb-js/zstd": "^1.1.0 || ^2.0.0",
|
| 1267 |
+
"gcp-metadata": "^5.2.0",
|
| 1268 |
+
"kerberos": "^2.0.1",
|
| 1269 |
+
"mongodb-client-encryption": ">=6.0.0 <7",
|
| 1270 |
+
"snappy": "^7.2.2",
|
| 1271 |
+
"socks": "^2.7.1"
|
| 1272 |
+
},
|
| 1273 |
+
"peerDependenciesMeta": {
|
| 1274 |
+
"@aws-sdk/credential-providers": {
|
| 1275 |
+
"optional": true
|
| 1276 |
+
},
|
| 1277 |
+
"@mongodb-js/zstd": {
|
| 1278 |
+
"optional": true
|
| 1279 |
+
},
|
| 1280 |
+
"gcp-metadata": {
|
| 1281 |
+
"optional": true
|
| 1282 |
+
},
|
| 1283 |
+
"kerberos": {
|
| 1284 |
+
"optional": true
|
| 1285 |
+
},
|
| 1286 |
+
"mongodb-client-encryption": {
|
| 1287 |
+
"optional": true
|
| 1288 |
+
},
|
| 1289 |
+
"snappy": {
|
| 1290 |
+
"optional": true
|
| 1291 |
+
},
|
| 1292 |
+
"socks": {
|
| 1293 |
+
"optional": true
|
| 1294 |
+
}
|
| 1295 |
+
}
|
| 1296 |
+
},
|
| 1297 |
+
"node_modules/mongodb-connection-string-url": {
|
| 1298 |
+
"version": "3.0.2",
|
| 1299 |
+
"resolved": "https://registry.npmjs.org/mongodb-connection-string-url/-/mongodb-connection-string-url-3.0.2.tgz",
|
| 1300 |
+
"integrity": "sha512-rMO7CGo/9BFwyZABcKAWL8UJwH/Kc2x0g72uhDWzG48URRax5TCIcJ7Rc3RZqffZzO/Gwff/jyKwCU9TN8gehA==",
|
| 1301 |
+
"license": "Apache-2.0",
|
| 1302 |
+
"dependencies": {
|
| 1303 |
+
"@types/whatwg-url": "^11.0.2",
|
| 1304 |
+
"whatwg-url": "^14.1.0 || ^13.0.0"
|
| 1305 |
+
}
|
| 1306 |
+
},
|
| 1307 |
+
"node_modules/mongoose": {
|
| 1308 |
+
"version": "8.16.4",
|
| 1309 |
+
"resolved": "https://registry.npmjs.org/mongoose/-/mongoose-8.16.4.tgz",
|
| 1310 |
+
"integrity": "sha512-jslgdQ8pY2vcNSKPv3Dbi5ogo/NT8zcvf6kPDyD8Sdsjsa1at3AFAF0F5PT+jySPGSPbvlNaQ49nT9h+Kx2UDA==",
|
| 1311 |
+
"license": "MIT",
|
| 1312 |
+
"dependencies": {
|
| 1313 |
+
"bson": "^6.10.4",
|
| 1314 |
+
"kareem": "2.6.3",
|
| 1315 |
+
"mongodb": "~6.17.0",
|
| 1316 |
+
"mpath": "0.9.0",
|
| 1317 |
+
"mquery": "5.0.0",
|
| 1318 |
+
"ms": "2.1.3",
|
| 1319 |
+
"sift": "17.1.3"
|
| 1320 |
+
},
|
| 1321 |
+
"engines": {
|
| 1322 |
+
"node": ">=16.20.1"
|
| 1323 |
+
},
|
| 1324 |
+
"funding": {
|
| 1325 |
+
"type": "opencollective",
|
| 1326 |
+
"url": "https://opencollective.com/mongoose"
|
| 1327 |
+
}
|
| 1328 |
+
},
|
| 1329 |
+
"node_modules/mongoose/node_modules/ms": {
|
| 1330 |
+
"version": "2.1.3",
|
| 1331 |
+
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
|
| 1332 |
+
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
|
| 1333 |
+
"license": "MIT"
|
| 1334 |
+
},
|
| 1335 |
+
"node_modules/mpath": {
|
| 1336 |
+
"version": "0.9.0",
|
| 1337 |
+
"resolved": "https://registry.npmjs.org/mpath/-/mpath-0.9.0.tgz",
|
| 1338 |
+
"integrity": "sha512-ikJRQTk8hw5DEoFVxHG1Gn9T/xcjtdnOKIU1JTmGjZZlg9LST2mBLmcX3/ICIbgJydT2GOc15RnNy5mHmzfSew==",
|
| 1339 |
+
"license": "MIT",
|
| 1340 |
+
"engines": {
|
| 1341 |
+
"node": ">=4.0.0"
|
| 1342 |
+
}
|
| 1343 |
+
},
|
| 1344 |
+
"node_modules/mquery": {
|
| 1345 |
+
"version": "5.0.0",
|
| 1346 |
+
"resolved": "https://registry.npmjs.org/mquery/-/mquery-5.0.0.tgz",
|
| 1347 |
+
"integrity": "sha512-iQMncpmEK8R8ncT8HJGsGc9Dsp8xcgYMVSbs5jgnm1lFHTZqMJTUWTDx1LBO8+mK3tPNZWFLBghQEIOULSTHZg==",
|
| 1348 |
+
"license": "MIT",
|
| 1349 |
+
"dependencies": {
|
| 1350 |
+
"debug": "4.x"
|
| 1351 |
+
},
|
| 1352 |
+
"engines": {
|
| 1353 |
+
"node": ">=14.0.0"
|
| 1354 |
+
}
|
| 1355 |
+
},
|
| 1356 |
+
"node_modules/mquery/node_modules/debug": {
|
| 1357 |
+
"version": "4.4.1",
|
| 1358 |
+
"resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz",
|
| 1359 |
+
"integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==",
|
| 1360 |
+
"license": "MIT",
|
| 1361 |
+
"dependencies": {
|
| 1362 |
+
"ms": "^2.1.3"
|
| 1363 |
+
},
|
| 1364 |
+
"engines": {
|
| 1365 |
+
"node": ">=6.0"
|
| 1366 |
+
},
|
| 1367 |
+
"peerDependenciesMeta": {
|
| 1368 |
+
"supports-color": {
|
| 1369 |
+
"optional": true
|
| 1370 |
+
}
|
| 1371 |
+
}
|
| 1372 |
+
},
|
| 1373 |
+
"node_modules/mquery/node_modules/ms": {
|
| 1374 |
+
"version": "2.1.3",
|
| 1375 |
+
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
|
| 1376 |
+
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
|
| 1377 |
+
"license": "MIT"
|
| 1378 |
+
},
|
| 1379 |
+
"node_modules/ms": {
|
| 1380 |
+
"version": "2.0.0",
|
| 1381 |
+
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
|
| 1382 |
+
"integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==",
|
| 1383 |
+
"license": "MIT"
|
| 1384 |
+
},
|
| 1385 |
+
"node_modules/negotiator": {
|
| 1386 |
+
"version": "0.6.3",
|
| 1387 |
+
"resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz",
|
| 1388 |
+
"integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==",
|
| 1389 |
+
"license": "MIT",
|
| 1390 |
+
"engines": {
|
| 1391 |
+
"node": ">= 0.6"
|
| 1392 |
+
}
|
| 1393 |
+
},
|
| 1394 |
+
"node_modules/nodemon": {
|
| 1395 |
+
"version": "3.1.10",
|
| 1396 |
+
"resolved": "https://registry.npmjs.org/nodemon/-/nodemon-3.1.10.tgz",
|
| 1397 |
+
"integrity": "sha512-WDjw3pJ0/0jMFmyNDp3gvY2YizjLmmOUQo6DEBY+JgdvW/yQ9mEeSw6H5ythl5Ny2ytb7f9C2nIbjSxMNzbJXw==",
|
| 1398 |
+
"dev": true,
|
| 1399 |
+
"license": "MIT",
|
| 1400 |
+
"dependencies": {
|
| 1401 |
+
"chokidar": "^3.5.2",
|
| 1402 |
+
"debug": "^4",
|
| 1403 |
+
"ignore-by-default": "^1.0.1",
|
| 1404 |
+
"minimatch": "^3.1.2",
|
| 1405 |
+
"pstree.remy": "^1.1.8",
|
| 1406 |
+
"semver": "^7.5.3",
|
| 1407 |
+
"simple-update-notifier": "^2.0.0",
|
| 1408 |
+
"supports-color": "^5.5.0",
|
| 1409 |
+
"touch": "^3.1.0",
|
| 1410 |
+
"undefsafe": "^2.0.5"
|
| 1411 |
+
},
|
| 1412 |
+
"bin": {
|
| 1413 |
+
"nodemon": "bin/nodemon.js"
|
| 1414 |
+
},
|
| 1415 |
+
"engines": {
|
| 1416 |
+
"node": ">=10"
|
| 1417 |
+
},
|
| 1418 |
+
"funding": {
|
| 1419 |
+
"type": "opencollective",
|
| 1420 |
+
"url": "https://opencollective.com/nodemon"
|
| 1421 |
+
}
|
| 1422 |
+
},
|
| 1423 |
+
"node_modules/nodemon/node_modules/debug": {
|
| 1424 |
+
"version": "4.4.1",
|
| 1425 |
+
"resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz",
|
| 1426 |
+
"integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==",
|
| 1427 |
+
"dev": true,
|
| 1428 |
+
"license": "MIT",
|
| 1429 |
+
"dependencies": {
|
| 1430 |
+
"ms": "^2.1.3"
|
| 1431 |
+
},
|
| 1432 |
+
"engines": {
|
| 1433 |
+
"node": ">=6.0"
|
| 1434 |
+
},
|
| 1435 |
+
"peerDependenciesMeta": {
|
| 1436 |
+
"supports-color": {
|
| 1437 |
+
"optional": true
|
| 1438 |
+
}
|
| 1439 |
+
}
|
| 1440 |
+
},
|
| 1441 |
+
"node_modules/nodemon/node_modules/ms": {
|
| 1442 |
+
"version": "2.1.3",
|
| 1443 |
+
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
|
| 1444 |
+
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
|
| 1445 |
+
"dev": true,
|
| 1446 |
+
"license": "MIT"
|
| 1447 |
+
},
|
| 1448 |
+
"node_modules/normalize-path": {
|
| 1449 |
+
"version": "3.0.0",
|
| 1450 |
+
"resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz",
|
| 1451 |
+
"integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==",
|
| 1452 |
+
"dev": true,
|
| 1453 |
+
"license": "MIT",
|
| 1454 |
+
"engines": {
|
| 1455 |
+
"node": ">=0.10.0"
|
| 1456 |
+
}
|
| 1457 |
+
},
|
| 1458 |
+
"node_modules/nth-check": {
|
| 1459 |
+
"version": "2.1.1",
|
| 1460 |
+
"resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz",
|
| 1461 |
+
"integrity": "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==",
|
| 1462 |
+
"license": "BSD-2-Clause",
|
| 1463 |
+
"dependencies": {
|
| 1464 |
+
"boolbase": "^1.0.0"
|
| 1465 |
+
},
|
| 1466 |
+
"funding": {
|
| 1467 |
+
"url": "https://github.com/fb55/nth-check?sponsor=1"
|
| 1468 |
+
}
|
| 1469 |
+
},
|
| 1470 |
+
"node_modules/object-assign": {
|
| 1471 |
+
"version": "4.1.1",
|
| 1472 |
+
"resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
|
| 1473 |
+
"integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==",
|
| 1474 |
+
"license": "MIT",
|
| 1475 |
+
"engines": {
|
| 1476 |
+
"node": ">=0.10.0"
|
| 1477 |
+
}
|
| 1478 |
+
},
|
| 1479 |
+
"node_modules/object-inspect": {
|
| 1480 |
+
"version": "1.13.4",
|
| 1481 |
+
"resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz",
|
| 1482 |
+
"integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==",
|
| 1483 |
+
"license": "MIT",
|
| 1484 |
+
"engines": {
|
| 1485 |
+
"node": ">= 0.4"
|
| 1486 |
+
},
|
| 1487 |
+
"funding": {
|
| 1488 |
+
"url": "https://github.com/sponsors/ljharb"
|
| 1489 |
+
}
|
| 1490 |
+
},
|
| 1491 |
+
"node_modules/on-finished": {
|
| 1492 |
+
"version": "2.4.1",
|
| 1493 |
+
"resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz",
|
| 1494 |
+
"integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==",
|
| 1495 |
+
"license": "MIT",
|
| 1496 |
+
"dependencies": {
|
| 1497 |
+
"ee-first": "1.1.1"
|
| 1498 |
+
},
|
| 1499 |
+
"engines": {
|
| 1500 |
+
"node": ">= 0.8"
|
| 1501 |
+
}
|
| 1502 |
+
},
|
| 1503 |
+
"node_modules/parse5": {
|
| 1504 |
+
"version": "7.3.0",
|
| 1505 |
+
"resolved": "https://registry.npmjs.org/parse5/-/parse5-7.3.0.tgz",
|
| 1506 |
+
"integrity": "sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw==",
|
| 1507 |
+
"license": "MIT",
|
| 1508 |
+
"dependencies": {
|
| 1509 |
+
"entities": "^6.0.0"
|
| 1510 |
+
},
|
| 1511 |
+
"funding": {
|
| 1512 |
+
"url": "https://github.com/inikulin/parse5?sponsor=1"
|
| 1513 |
+
}
|
| 1514 |
+
},
|
| 1515 |
+
"node_modules/parse5-htmlparser2-tree-adapter": {
|
| 1516 |
+
"version": "7.1.0",
|
| 1517 |
+
"resolved": "https://registry.npmjs.org/parse5-htmlparser2-tree-adapter/-/parse5-htmlparser2-tree-adapter-7.1.0.tgz",
|
| 1518 |
+
"integrity": "sha512-ruw5xyKs6lrpo9x9rCZqZZnIUntICjQAd0Wsmp396Ul9lN/h+ifgVV1x1gZHi8euej6wTfpqX8j+BFQxF0NS/g==",
|
| 1519 |
+
"license": "MIT",
|
| 1520 |
+
"dependencies": {
|
| 1521 |
+
"domhandler": "^5.0.3",
|
| 1522 |
+
"parse5": "^7.0.0"
|
| 1523 |
+
},
|
| 1524 |
+
"funding": {
|
| 1525 |
+
"url": "https://github.com/inikulin/parse5?sponsor=1"
|
| 1526 |
+
}
|
| 1527 |
+
},
|
| 1528 |
+
"node_modules/parse5-parser-stream": {
|
| 1529 |
+
"version": "7.1.2",
|
| 1530 |
+
"resolved": "https://registry.npmjs.org/parse5-parser-stream/-/parse5-parser-stream-7.1.2.tgz",
|
| 1531 |
+
"integrity": "sha512-JyeQc9iwFLn5TbvvqACIF/VXG6abODeB3Fwmv/TGdLk2LfbWkaySGY72at4+Ty7EkPZj854u4CrICqNk2qIbow==",
|
| 1532 |
+
"license": "MIT",
|
| 1533 |
+
"dependencies": {
|
| 1534 |
+
"parse5": "^7.0.0"
|
| 1535 |
+
},
|
| 1536 |
+
"funding": {
|
| 1537 |
+
"url": "https://github.com/inikulin/parse5?sponsor=1"
|
| 1538 |
+
}
|
| 1539 |
+
},
|
| 1540 |
+
"node_modules/parse5/node_modules/entities": {
|
| 1541 |
+
"version": "6.0.1",
|
| 1542 |
+
"resolved": "https://registry.npmjs.org/entities/-/entities-6.0.1.tgz",
|
| 1543 |
+
"integrity": "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==",
|
| 1544 |
+
"license": "BSD-2-Clause",
|
| 1545 |
+
"engines": {
|
| 1546 |
+
"node": ">=0.12"
|
| 1547 |
+
},
|
| 1548 |
+
"funding": {
|
| 1549 |
+
"url": "https://github.com/fb55/entities?sponsor=1"
|
| 1550 |
+
}
|
| 1551 |
+
},
|
| 1552 |
+
"node_modules/parseurl": {
|
| 1553 |
+
"version": "1.3.3",
|
| 1554 |
+
"resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz",
|
| 1555 |
+
"integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==",
|
| 1556 |
+
"license": "MIT",
|
| 1557 |
+
"engines": {
|
| 1558 |
+
"node": ">= 0.8"
|
| 1559 |
+
}
|
| 1560 |
+
},
|
| 1561 |
+
"node_modules/path-to-regexp": {
|
| 1562 |
+
"version": "0.1.12",
|
| 1563 |
+
"resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.12.tgz",
|
| 1564 |
+
"integrity": "sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==",
|
| 1565 |
+
"license": "MIT"
|
| 1566 |
+
},
|
| 1567 |
+
"node_modules/picomatch": {
|
| 1568 |
+
"version": "2.3.1",
|
| 1569 |
+
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz",
|
| 1570 |
+
"integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==",
|
| 1571 |
+
"dev": true,
|
| 1572 |
+
"license": "MIT",
|
| 1573 |
+
"engines": {
|
| 1574 |
+
"node": ">=8.6"
|
| 1575 |
+
},
|
| 1576 |
+
"funding": {
|
| 1577 |
+
"url": "https://github.com/sponsors/jonschlinkert"
|
| 1578 |
+
}
|
| 1579 |
+
},
|
| 1580 |
+
"node_modules/proxy-addr": {
|
| 1581 |
+
"version": "2.0.7",
|
| 1582 |
+
"resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz",
|
| 1583 |
+
"integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==",
|
| 1584 |
+
"license": "MIT",
|
| 1585 |
+
"dependencies": {
|
| 1586 |
+
"forwarded": "0.2.0",
|
| 1587 |
+
"ipaddr.js": "1.9.1"
|
| 1588 |
+
},
|
| 1589 |
+
"engines": {
|
| 1590 |
+
"node": ">= 0.10"
|
| 1591 |
+
}
|
| 1592 |
+
},
|
| 1593 |
+
"node_modules/proxy-from-env": {
|
| 1594 |
+
"version": "1.1.0",
|
| 1595 |
+
"resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
|
| 1596 |
+
"integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==",
|
| 1597 |
+
"license": "MIT"
|
| 1598 |
+
},
|
| 1599 |
+
"node_modules/pstree.remy": {
|
| 1600 |
+
"version": "1.1.8",
|
| 1601 |
+
"resolved": "https://registry.npmjs.org/pstree.remy/-/pstree.remy-1.1.8.tgz",
|
| 1602 |
+
"integrity": "sha512-77DZwxQmxKnu3aR542U+X8FypNzbfJ+C5XQDk3uWjWxn6151aIMGthWYRXTqT1E5oJvg+ljaa2OJi+VfvCOQ8w==",
|
| 1603 |
+
"dev": true,
|
| 1604 |
+
"license": "MIT"
|
| 1605 |
+
},
|
| 1606 |
+
"node_modules/punycode": {
|
| 1607 |
+
"version": "2.3.1",
|
| 1608 |
+
"resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz",
|
| 1609 |
+
"integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==",
|
| 1610 |
+
"license": "MIT",
|
| 1611 |
+
"engines": {
|
| 1612 |
+
"node": ">=6"
|
| 1613 |
+
}
|
| 1614 |
+
},
|
| 1615 |
+
"node_modules/qs": {
|
| 1616 |
+
"version": "6.13.0",
|
| 1617 |
+
"resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz",
|
| 1618 |
+
"integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==",
|
| 1619 |
+
"license": "BSD-3-Clause",
|
| 1620 |
+
"dependencies": {
|
| 1621 |
+
"side-channel": "^1.0.6"
|
| 1622 |
+
},
|
| 1623 |
+
"engines": {
|
| 1624 |
+
"node": ">=0.6"
|
| 1625 |
+
},
|
| 1626 |
+
"funding": {
|
| 1627 |
+
"url": "https://github.com/sponsors/ljharb"
|
| 1628 |
+
}
|
| 1629 |
+
},
|
| 1630 |
+
"node_modules/range-parser": {
|
| 1631 |
+
"version": "1.2.1",
|
| 1632 |
+
"resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz",
|
| 1633 |
+
"integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==",
|
| 1634 |
+
"license": "MIT",
|
| 1635 |
+
"engines": {
|
| 1636 |
+
"node": ">= 0.6"
|
| 1637 |
+
}
|
| 1638 |
+
},
|
| 1639 |
+
"node_modules/raw-body": {
|
| 1640 |
+
"version": "2.5.2",
|
| 1641 |
+
"resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz",
|
| 1642 |
+
"integrity": "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==",
|
| 1643 |
+
"license": "MIT",
|
| 1644 |
+
"dependencies": {
|
| 1645 |
+
"bytes": "3.1.2",
|
| 1646 |
+
"http-errors": "2.0.0",
|
| 1647 |
+
"iconv-lite": "0.4.24",
|
| 1648 |
+
"unpipe": "1.0.0"
|
| 1649 |
+
},
|
| 1650 |
+
"engines": {
|
| 1651 |
+
"node": ">= 0.8"
|
| 1652 |
+
}
|
| 1653 |
+
},
|
| 1654 |
+
"node_modules/raw-body/node_modules/iconv-lite": {
|
| 1655 |
+
"version": "0.4.24",
|
| 1656 |
+
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz",
|
| 1657 |
+
"integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==",
|
| 1658 |
+
"license": "MIT",
|
| 1659 |
+
"dependencies": {
|
| 1660 |
+
"safer-buffer": ">= 2.1.2 < 3"
|
| 1661 |
+
},
|
| 1662 |
+
"engines": {
|
| 1663 |
+
"node": ">=0.10.0"
|
| 1664 |
+
}
|
| 1665 |
+
},
|
| 1666 |
+
"node_modules/readdirp": {
|
| 1667 |
+
"version": "3.6.0",
|
| 1668 |
+
"resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz",
|
| 1669 |
+
"integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==",
|
| 1670 |
+
"dev": true,
|
| 1671 |
+
"license": "MIT",
|
| 1672 |
+
"dependencies": {
|
| 1673 |
+
"picomatch": "^2.2.1"
|
| 1674 |
+
},
|
| 1675 |
+
"engines": {
|
| 1676 |
+
"node": ">=8.10.0"
|
| 1677 |
+
}
|
| 1678 |
+
},
|
| 1679 |
+
"node_modules/safe-buffer": {
|
| 1680 |
+
"version": "5.2.1",
|
| 1681 |
+
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
|
| 1682 |
+
"integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==",
|
| 1683 |
+
"funding": [
|
| 1684 |
+
{
|
| 1685 |
+
"type": "github",
|
| 1686 |
+
"url": "https://github.com/sponsors/feross"
|
| 1687 |
+
},
|
| 1688 |
+
{
|
| 1689 |
+
"type": "patreon",
|
| 1690 |
+
"url": "https://www.patreon.com/feross"
|
| 1691 |
+
},
|
| 1692 |
+
{
|
| 1693 |
+
"type": "consulting",
|
| 1694 |
+
"url": "https://feross.org/support"
|
| 1695 |
+
}
|
| 1696 |
+
],
|
| 1697 |
+
"license": "MIT"
|
| 1698 |
+
},
|
| 1699 |
+
"node_modules/safer-buffer": {
|
| 1700 |
+
"version": "2.1.2",
|
| 1701 |
+
"resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
|
| 1702 |
+
"integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==",
|
| 1703 |
+
"license": "MIT"
|
| 1704 |
+
},
|
| 1705 |
+
"node_modules/semver": {
|
| 1706 |
+
"version": "7.7.2",
|
| 1707 |
+
"resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz",
|
| 1708 |
+
"integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==",
|
| 1709 |
+
"license": "ISC",
|
| 1710 |
+
"bin": {
|
| 1711 |
+
"semver": "bin/semver.js"
|
| 1712 |
+
},
|
| 1713 |
+
"engines": {
|
| 1714 |
+
"node": ">=10"
|
| 1715 |
+
}
|
| 1716 |
+
},
|
| 1717 |
+
"node_modules/send": {
|
| 1718 |
+
"version": "0.19.0",
|
| 1719 |
+
"resolved": "https://registry.npmjs.org/send/-/send-0.19.0.tgz",
|
| 1720 |
+
"integrity": "sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==",
|
| 1721 |
+
"license": "MIT",
|
| 1722 |
+
"dependencies": {
|
| 1723 |
+
"debug": "2.6.9",
|
| 1724 |
+
"depd": "2.0.0",
|
| 1725 |
+
"destroy": "1.2.0",
|
| 1726 |
+
"encodeurl": "~1.0.2",
|
| 1727 |
+
"escape-html": "~1.0.3",
|
| 1728 |
+
"etag": "~1.8.1",
|
| 1729 |
+
"fresh": "0.5.2",
|
| 1730 |
+
"http-errors": "2.0.0",
|
| 1731 |
+
"mime": "1.6.0",
|
| 1732 |
+
"ms": "2.1.3",
|
| 1733 |
+
"on-finished": "2.4.1",
|
| 1734 |
+
"range-parser": "~1.2.1",
|
| 1735 |
+
"statuses": "2.0.1"
|
| 1736 |
+
},
|
| 1737 |
+
"engines": {
|
| 1738 |
+
"node": ">= 0.8.0"
|
| 1739 |
+
}
|
| 1740 |
+
},
|
| 1741 |
+
"node_modules/send/node_modules/encodeurl": {
|
| 1742 |
+
"version": "1.0.2",
|
| 1743 |
+
"resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz",
|
| 1744 |
+
"integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==",
|
| 1745 |
+
"license": "MIT",
|
| 1746 |
+
"engines": {
|
| 1747 |
+
"node": ">= 0.8"
|
| 1748 |
+
}
|
| 1749 |
+
},
|
| 1750 |
+
"node_modules/send/node_modules/ms": {
|
| 1751 |
+
"version": "2.1.3",
|
| 1752 |
+
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
|
| 1753 |
+
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
|
| 1754 |
+
"license": "MIT"
|
| 1755 |
+
},
|
| 1756 |
+
"node_modules/serve-static": {
|
| 1757 |
+
"version": "1.16.2",
|
| 1758 |
+
"resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.2.tgz",
|
| 1759 |
+
"integrity": "sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw==",
|
| 1760 |
+
"license": "MIT",
|
| 1761 |
+
"dependencies": {
|
| 1762 |
+
"encodeurl": "~2.0.0",
|
| 1763 |
+
"escape-html": "~1.0.3",
|
| 1764 |
+
"parseurl": "~1.3.3",
|
| 1765 |
+
"send": "0.19.0"
|
| 1766 |
+
},
|
| 1767 |
+
"engines": {
|
| 1768 |
+
"node": ">= 0.8.0"
|
| 1769 |
+
}
|
| 1770 |
+
},
|
| 1771 |
+
"node_modules/setprototypeof": {
|
| 1772 |
+
"version": "1.2.0",
|
| 1773 |
+
"resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz",
|
| 1774 |
+
"integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==",
|
| 1775 |
+
"license": "ISC"
|
| 1776 |
+
},
|
| 1777 |
+
"node_modules/side-channel": {
|
| 1778 |
+
"version": "1.1.0",
|
| 1779 |
+
"resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz",
|
| 1780 |
+
"integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==",
|
| 1781 |
+
"license": "MIT",
|
| 1782 |
+
"dependencies": {
|
| 1783 |
+
"es-errors": "^1.3.0",
|
| 1784 |
+
"object-inspect": "^1.13.3",
|
| 1785 |
+
"side-channel-list": "^1.0.0",
|
| 1786 |
+
"side-channel-map": "^1.0.1",
|
| 1787 |
+
"side-channel-weakmap": "^1.0.2"
|
| 1788 |
+
},
|
| 1789 |
+
"engines": {
|
| 1790 |
+
"node": ">= 0.4"
|
| 1791 |
+
},
|
| 1792 |
+
"funding": {
|
| 1793 |
+
"url": "https://github.com/sponsors/ljharb"
|
| 1794 |
+
}
|
| 1795 |
+
},
|
| 1796 |
+
"node_modules/side-channel-list": {
|
| 1797 |
+
"version": "1.0.0",
|
| 1798 |
+
"resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz",
|
| 1799 |
+
"integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==",
|
| 1800 |
+
"license": "MIT",
|
| 1801 |
+
"dependencies": {
|
| 1802 |
+
"es-errors": "^1.3.0",
|
| 1803 |
+
"object-inspect": "^1.13.3"
|
| 1804 |
+
},
|
| 1805 |
+
"engines": {
|
| 1806 |
+
"node": ">= 0.4"
|
| 1807 |
+
},
|
| 1808 |
+
"funding": {
|
| 1809 |
+
"url": "https://github.com/sponsors/ljharb"
|
| 1810 |
+
}
|
| 1811 |
+
},
|
| 1812 |
+
"node_modules/side-channel-map": {
|
| 1813 |
+
"version": "1.0.1",
|
| 1814 |
+
"resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz",
|
| 1815 |
+
"integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==",
|
| 1816 |
+
"license": "MIT",
|
| 1817 |
+
"dependencies": {
|
| 1818 |
+
"call-bound": "^1.0.2",
|
| 1819 |
+
"es-errors": "^1.3.0",
|
| 1820 |
+
"get-intrinsic": "^1.2.5",
|
| 1821 |
+
"object-inspect": "^1.13.3"
|
| 1822 |
+
},
|
| 1823 |
+
"engines": {
|
| 1824 |
+
"node": ">= 0.4"
|
| 1825 |
+
},
|
| 1826 |
+
"funding": {
|
| 1827 |
+
"url": "https://github.com/sponsors/ljharb"
|
| 1828 |
+
}
|
| 1829 |
+
},
|
| 1830 |
+
"node_modules/side-channel-weakmap": {
|
| 1831 |
+
"version": "1.0.2",
|
| 1832 |
+
"resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz",
|
| 1833 |
+
"integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==",
|
| 1834 |
+
"license": "MIT",
|
| 1835 |
+
"dependencies": {
|
| 1836 |
+
"call-bound": "^1.0.2",
|
| 1837 |
+
"es-errors": "^1.3.0",
|
| 1838 |
+
"get-intrinsic": "^1.2.5",
|
| 1839 |
+
"object-inspect": "^1.13.3",
|
| 1840 |
+
"side-channel-map": "^1.0.1"
|
| 1841 |
+
},
|
| 1842 |
+
"engines": {
|
| 1843 |
+
"node": ">= 0.4"
|
| 1844 |
+
},
|
| 1845 |
+
"funding": {
|
| 1846 |
+
"url": "https://github.com/sponsors/ljharb"
|
| 1847 |
+
}
|
| 1848 |
+
},
|
| 1849 |
+
"node_modules/sift": {
|
| 1850 |
+
"version": "17.1.3",
|
| 1851 |
+
"resolved": "https://registry.npmjs.org/sift/-/sift-17.1.3.tgz",
|
| 1852 |
+
"integrity": "sha512-Rtlj66/b0ICeFzYTuNvX/EF1igRbbnGSvEyT79McoZa/DeGhMyC5pWKOEsZKnpkqtSeovd5FL/bjHWC3CIIvCQ==",
|
| 1853 |
+
"license": "MIT"
|
| 1854 |
+
},
|
| 1855 |
+
"node_modules/simple-update-notifier": {
|
| 1856 |
+
"version": "2.0.0",
|
| 1857 |
+
"resolved": "https://registry.npmjs.org/simple-update-notifier/-/simple-update-notifier-2.0.0.tgz",
|
| 1858 |
+
"integrity": "sha512-a2B9Y0KlNXl9u/vsW6sTIu9vGEpfKu2wRV6l1H3XEas/0gUIzGzBoP/IouTcUQbm9JWZLH3COxyn03TYlFax6w==",
|
| 1859 |
+
"dev": true,
|
| 1860 |
+
"license": "MIT",
|
| 1861 |
+
"dependencies": {
|
| 1862 |
+
"semver": "^7.5.3"
|
| 1863 |
+
},
|
| 1864 |
+
"engines": {
|
| 1865 |
+
"node": ">=10"
|
| 1866 |
+
}
|
| 1867 |
+
},
|
| 1868 |
+
"node_modules/sparse-bitfield": {
|
| 1869 |
+
"version": "3.0.3",
|
| 1870 |
+
"resolved": "https://registry.npmjs.org/sparse-bitfield/-/sparse-bitfield-3.0.3.tgz",
|
| 1871 |
+
"integrity": "sha512-kvzhi7vqKTfkh0PZU+2D2PIllw2ymqJKujUcyPMd9Y75Nv4nPbGJZXNhxsgdQab2BmlDct1YnfQCguEvHr7VsQ==",
|
| 1872 |
+
"license": "MIT",
|
| 1873 |
+
"dependencies": {
|
| 1874 |
+
"memory-pager": "^1.0.2"
|
| 1875 |
+
}
|
| 1876 |
+
},
|
| 1877 |
+
"node_modules/statuses": {
|
| 1878 |
+
"version": "2.0.1",
|
| 1879 |
+
"resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz",
|
| 1880 |
+
"integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==",
|
| 1881 |
+
"license": "MIT",
|
| 1882 |
+
"engines": {
|
| 1883 |
+
"node": ">= 0.8"
|
| 1884 |
+
}
|
| 1885 |
+
},
|
| 1886 |
+
"node_modules/supports-color": {
|
| 1887 |
+
"version": "5.5.0",
|
| 1888 |
+
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
|
| 1889 |
+
"integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==",
|
| 1890 |
+
"dev": true,
|
| 1891 |
+
"license": "MIT",
|
| 1892 |
+
"dependencies": {
|
| 1893 |
+
"has-flag": "^3.0.0"
|
| 1894 |
+
},
|
| 1895 |
+
"engines": {
|
| 1896 |
+
"node": ">=4"
|
| 1897 |
+
}
|
| 1898 |
+
},
|
| 1899 |
+
"node_modules/to-regex-range": {
|
| 1900 |
+
"version": "5.0.1",
|
| 1901 |
+
"resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
|
| 1902 |
+
"integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==",
|
| 1903 |
+
"dev": true,
|
| 1904 |
+
"license": "MIT",
|
| 1905 |
+
"dependencies": {
|
| 1906 |
+
"is-number": "^7.0.0"
|
| 1907 |
+
},
|
| 1908 |
+
"engines": {
|
| 1909 |
+
"node": ">=8.0"
|
| 1910 |
+
}
|
| 1911 |
+
},
|
| 1912 |
+
"node_modules/toidentifier": {
|
| 1913 |
+
"version": "1.0.1",
|
| 1914 |
+
"resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz",
|
| 1915 |
+
"integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==",
|
| 1916 |
+
"license": "MIT",
|
| 1917 |
+
"engines": {
|
| 1918 |
+
"node": ">=0.6"
|
| 1919 |
+
}
|
| 1920 |
+
},
|
| 1921 |
+
"node_modules/touch": {
|
| 1922 |
+
"version": "3.1.1",
|
| 1923 |
+
"resolved": "https://registry.npmjs.org/touch/-/touch-3.1.1.tgz",
|
| 1924 |
+
"integrity": "sha512-r0eojU4bI8MnHr8c5bNo7lJDdI2qXlWWJk6a9EAFG7vbhTjElYhBVS3/miuE0uOuoLdb8Mc/rVfsmm6eo5o9GA==",
|
| 1925 |
+
"dev": true,
|
| 1926 |
+
"license": "ISC",
|
| 1927 |
+
"bin": {
|
| 1928 |
+
"nodetouch": "bin/nodetouch.js"
|
| 1929 |
+
}
|
| 1930 |
+
},
|
| 1931 |
+
"node_modules/tr46": {
|
| 1932 |
+
"version": "5.1.1",
|
| 1933 |
+
"resolved": "https://registry.npmjs.org/tr46/-/tr46-5.1.1.tgz",
|
| 1934 |
+
"integrity": "sha512-hdF5ZgjTqgAntKkklYw0R03MG2x/bSzTtkxmIRw/sTNV8YXsCJ1tfLAX23lhxhHJlEf3CRCOCGGWw3vI3GaSPw==",
|
| 1935 |
+
"license": "MIT",
|
| 1936 |
+
"dependencies": {
|
| 1937 |
+
"punycode": "^2.3.1"
|
| 1938 |
+
},
|
| 1939 |
+
"engines": {
|
| 1940 |
+
"node": ">=18"
|
| 1941 |
+
}
|
| 1942 |
+
},
|
| 1943 |
+
"node_modules/type-is": {
|
| 1944 |
+
"version": "1.6.18",
|
| 1945 |
+
"resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz",
|
| 1946 |
+
"integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==",
|
| 1947 |
+
"license": "MIT",
|
| 1948 |
+
"dependencies": {
|
| 1949 |
+
"media-typer": "0.3.0",
|
| 1950 |
+
"mime-types": "~2.1.24"
|
| 1951 |
+
},
|
| 1952 |
+
"engines": {
|
| 1953 |
+
"node": ">= 0.6"
|
| 1954 |
+
}
|
| 1955 |
+
},
|
| 1956 |
+
"node_modules/undefsafe": {
|
| 1957 |
+
"version": "2.0.5",
|
| 1958 |
+
"resolved": "https://registry.npmjs.org/undefsafe/-/undefsafe-2.0.5.tgz",
|
| 1959 |
+
"integrity": "sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA==",
|
| 1960 |
+
"dev": true,
|
| 1961 |
+
"license": "MIT"
|
| 1962 |
+
},
|
| 1963 |
+
"node_modules/undici": {
|
| 1964 |
+
"version": "7.12.0",
|
| 1965 |
+
"resolved": "https://registry.npmjs.org/undici/-/undici-7.12.0.tgz",
|
| 1966 |
+
"integrity": "sha512-GrKEsc3ughskmGA9jevVlIOPMiiAHJ4OFUtaAH+NhfTUSiZ1wMPIQqQvAJUrJspFXJt3EBWgpAeoHEDVT1IBug==",
|
| 1967 |
+
"license": "MIT",
|
| 1968 |
+
"engines": {
|
| 1969 |
+
"node": ">=20.18.1"
|
| 1970 |
+
}
|
| 1971 |
+
},
|
| 1972 |
+
"node_modules/unpipe": {
|
| 1973 |
+
"version": "1.0.0",
|
| 1974 |
+
"resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz",
|
| 1975 |
+
"integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==",
|
| 1976 |
+
"license": "MIT",
|
| 1977 |
+
"engines": {
|
| 1978 |
+
"node": ">= 0.8"
|
| 1979 |
+
}
|
| 1980 |
+
},
|
| 1981 |
+
"node_modules/utils-merge": {
|
| 1982 |
+
"version": "1.0.1",
|
| 1983 |
+
"resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz",
|
| 1984 |
+
"integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==",
|
| 1985 |
+
"license": "MIT",
|
| 1986 |
+
"engines": {
|
| 1987 |
+
"node": ">= 0.4.0"
|
| 1988 |
+
}
|
| 1989 |
+
},
|
| 1990 |
+
"node_modules/uuid": {
|
| 1991 |
+
"version": "9.0.1",
|
| 1992 |
+
"resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz",
|
| 1993 |
+
"integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==",
|
| 1994 |
+
"funding": [
|
| 1995 |
+
"https://github.com/sponsors/broofa",
|
| 1996 |
+
"https://github.com/sponsors/ctavan"
|
| 1997 |
+
],
|
| 1998 |
+
"license": "MIT",
|
| 1999 |
+
"bin": {
|
| 2000 |
+
"uuid": "dist/bin/uuid"
|
| 2001 |
+
}
|
| 2002 |
+
},
|
| 2003 |
+
"node_modules/validator": {
|
| 2004 |
+
"version": "13.12.0",
|
| 2005 |
+
"resolved": "https://registry.npmjs.org/validator/-/validator-13.12.0.tgz",
|
| 2006 |
+
"integrity": "sha512-c1Q0mCiPlgdTVVVIJIrBuxNicYE+t/7oKeI9MWLj3fh/uq2Pxh/3eeWbVZ4OcGW1TUf53At0njHw5SMdA3tmMg==",
|
| 2007 |
+
"license": "MIT",
|
| 2008 |
+
"engines": {
|
| 2009 |
+
"node": ">= 0.10"
|
| 2010 |
+
}
|
| 2011 |
+
},
|
| 2012 |
+
"node_modules/vary": {
|
| 2013 |
+
"version": "1.1.2",
|
| 2014 |
+
"resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz",
|
| 2015 |
+
"integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==",
|
| 2016 |
+
"license": "MIT",
|
| 2017 |
+
"engines": {
|
| 2018 |
+
"node": ">= 0.8"
|
| 2019 |
+
}
|
| 2020 |
+
},
|
| 2021 |
+
"node_modules/webidl-conversions": {
|
| 2022 |
+
"version": "7.0.0",
|
| 2023 |
+
"resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz",
|
| 2024 |
+
"integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==",
|
| 2025 |
+
"license": "BSD-2-Clause",
|
| 2026 |
+
"engines": {
|
| 2027 |
+
"node": ">=12"
|
| 2028 |
+
}
|
| 2029 |
+
},
|
| 2030 |
+
"node_modules/whatwg-encoding": {
|
| 2031 |
+
"version": "3.1.1",
|
| 2032 |
+
"resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-3.1.1.tgz",
|
| 2033 |
+
"integrity": "sha512-6qN4hJdMwfYBtE3YBTTHhoeuUrDBPZmbQaxWAqSALV/MeEnR5z1xd8UKud2RAkFoPkmB+hli1TZSnyi84xz1vQ==",
|
| 2034 |
+
"license": "MIT",
|
| 2035 |
+
"dependencies": {
|
| 2036 |
+
"iconv-lite": "0.6.3"
|
| 2037 |
+
},
|
| 2038 |
+
"engines": {
|
| 2039 |
+
"node": ">=18"
|
| 2040 |
+
}
|
| 2041 |
+
},
|
| 2042 |
+
"node_modules/whatwg-mimetype": {
|
| 2043 |
+
"version": "4.0.0",
|
| 2044 |
+
"resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-4.0.0.tgz",
|
| 2045 |
+
"integrity": "sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg==",
|
| 2046 |
+
"license": "MIT",
|
| 2047 |
+
"engines": {
|
| 2048 |
+
"node": ">=18"
|
| 2049 |
+
}
|
| 2050 |
+
},
|
| 2051 |
+
"node_modules/whatwg-url": {
|
| 2052 |
+
"version": "14.2.0",
|
| 2053 |
+
"resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-14.2.0.tgz",
|
| 2054 |
+
"integrity": "sha512-De72GdQZzNTUBBChsXueQUnPKDkg/5A5zp7pFDuQAj5UFoENpiACU0wlCvzpAGnTkj++ihpKwKyYewn/XNUbKw==",
|
| 2055 |
+
"license": "MIT",
|
| 2056 |
+
"dependencies": {
|
| 2057 |
+
"tr46": "^5.1.0",
|
| 2058 |
+
"webidl-conversions": "^7.0.0"
|
| 2059 |
+
},
|
| 2060 |
+
"engines": {
|
| 2061 |
+
"node": ">=18"
|
| 2062 |
+
}
|
| 2063 |
+
}
|
| 2064 |
+
}
|
| 2065 |
+
}
|
package.json
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"name": "transcreation-sandbox-server",
|
| 3 |
+
"version": "1.0.0",
|
| 4 |
+
"description": "Backend server for Transcreation Sandbox",
|
| 5 |
+
"main": "index.js",
|
| 6 |
+
"scripts": {
|
| 7 |
+
"start": "node index.js",
|
| 8 |
+
"dev": "nodemon index.js"
|
| 9 |
+
},
|
| 10 |
+
"dependencies": {
|
| 11 |
+
"express": "^4.18.2",
|
| 12 |
+
"cors": "^2.8.5",
|
| 13 |
+
"mongoose": "^8.0.3",
|
| 14 |
+
"dotenv": "^16.3.1",
|
| 15 |
+
"axios": "^1.6.2",
|
| 16 |
+
"cheerio": "^1.0.0-rc.12",
|
| 17 |
+
"uuid": "^9.0.1",
|
| 18 |
+
"bcryptjs": "^2.4.3",
|
| 19 |
+
"jsonwebtoken": "^9.0.2",
|
| 20 |
+
"express-rate-limit": "^7.1.5"
|
| 21 |
+
},
|
| 22 |
+
"devDependencies": {
|
| 23 |
+
"nodemon": "^3.0.2"
|
| 24 |
+
}
|
| 25 |
+
}
|
routes/auth.js
ADDED
|
@@ -0,0 +1,783 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
const express = require('express');
|
| 2 |
+
const router = express.Router();
|
| 3 |
+
|
| 4 |
+
// Pre-defined users
|
| 5 |
+
const PREDEFINED_USERS = {
|
| 6 |
+
'mche0278@student.monash.edu': { name: 'Michelle Chen', email: 'mche0278@student.monash.edu', role: 'student' },
|
| 7 |
+
'zfan0011@student.monash.edu': { name: 'Fang Zhou', email: 'zfan0011@student.monash.edu', role: 'student' },
|
| 8 |
+
'yfuu0071@student.monash.edu': { name: 'Fu Yitong', email: 'yfuu0071@student.monash.edu', role: 'student' },
|
| 9 |
+
'hhan0022@student.monash.edu': { name: 'Han Heyang', email: 'hhan0022@student.monash.edu', role: 'student' },
|
| 10 |
+
'ylei0024@student.monash.edu': { name: 'Lei Yang', email: 'ylei0024@student.monash.edu', role: 'student' },
|
| 11 |
+
'wlii0217@student.monash.edu': { name: 'Li Wenhui', email: 'wlii0217@student.monash.edu', role: 'student' },
|
| 12 |
+
'zlia0091@student.monash.edu': { name: 'Liao Zhixin', email: 'zlia0091@student.monash.edu', role: 'student' },
|
| 13 |
+
'hmaa0054@student.monash.edu': { name: 'Ma Huachen', email: 'hmaa0054@student.monash.edu', role: 'student' },
|
| 14 |
+
'csun0059@student.monash.edu': { name: 'Sun Chongkai', email: 'csun0059@student.monash.edu', role: 'student' },
|
| 15 |
+
'pwon0030@student.monash.edu': { name: 'Wong Prisca Si-Heng', email: 'pwon0030@student.monash.edu', role: 'student' },
|
| 16 |
+
'zxia0017@student.monash.edu': { name: 'Xia Zechen', email: 'zxia0017@student.monash.edu', role: 'student' },
|
| 17 |
+
'lyou0021@student.monash.edu': { name: 'You Lianxiang', email: 'lyou0021@student.monash.edu', role: 'student' },
|
| 18 |
+
'wzhe0034@student.monash.edu': { name: 'Zheng Weijie', email: 'wzhe0034@student.monash.edu', role: 'student' },
|
| 19 |
+
'szhe0055@student.monash.edu': { name: 'Zheng Siyuan', email: 'szhe0055@student.monash.edu', role: 'student' },
|
| 20 |
+
'xzhe0055@student.monash.edu': { name: 'Zheng Xiang', email: 'xzhe0055@student.monash.edu', role: 'student' },
|
| 21 |
+
'hongchang.yu@monash.edu': { name: 'Tristan', email: 'hongchang.yu@monash.edu', role: 'admin' }
|
| 22 |
+
};
|
| 23 |
+
|
| 24 |
+
// Middleware to verify token (simplified)
|
| 25 |
+
const authenticateToken = (req, res, next) => {
|
| 26 |
+
const authHeader = req.headers['authorization'];
|
| 27 |
+
const token = authHeader && authHeader.split(' ')[1];
|
| 28 |
+
|
| 29 |
+
if (!token) {
|
| 30 |
+
return res.status(401).json({ error: 'Access token required' });
|
| 31 |
+
}
|
| 32 |
+
|
| 33 |
+
// For our simplified system, just check if token exists and has the right format
|
| 34 |
+
if (token.startsWith('user_') || token.startsWith('visitor_')) {
|
| 35 |
+
req.user = { token }; // We'll get user details from localStorage on frontend
|
| 36 |
+
next();
|
| 37 |
+
} else {
|
| 38 |
+
return res.status(403).json({ error: 'Invalid token' });
|
| 39 |
+
}
|
| 40 |
+
};
|
| 41 |
+
|
| 42 |
+
// Middleware to check if user is admin
|
| 43 |
+
const requireAdmin = (req, res, next) => {
|
| 44 |
+
// In our simplified system, we'll check the user's role from the request body or headers
|
| 45 |
+
// The frontend will send the user role in the request
|
| 46 |
+
const userRole = req.headers['user-role'] || req.body.role;
|
| 47 |
+
|
| 48 |
+
if (userRole !== 'admin') {
|
| 49 |
+
return res.status(403).json({ error: 'Admin access required' });
|
| 50 |
+
}
|
| 51 |
+
|
| 52 |
+
next();
|
| 53 |
+
};
|
| 54 |
+
|
| 55 |
+
// Login endpoint (simplified)
|
| 56 |
+
router.post('/login', async (req, res) => {
|
| 57 |
+
try {
|
| 58 |
+
const { email } = req.body;
|
| 59 |
+
|
| 60 |
+
// Check if email is in predefined users
|
| 61 |
+
const user = PREDEFINED_USERS[email];
|
| 62 |
+
|
| 63 |
+
if (user) {
|
| 64 |
+
// For predefined users, create a simple token
|
| 65 |
+
const token = `user_${Date.now()}`;
|
| 66 |
+
res.json({
|
| 67 |
+
success: true,
|
| 68 |
+
token,
|
| 69 |
+
user: {
|
| 70 |
+
name: user.name,
|
| 71 |
+
email: user.email,
|
| 72 |
+
role: user.role
|
| 73 |
+
}
|
| 74 |
+
});
|
| 75 |
+
} else {
|
| 76 |
+
// For visitors, create a visitor account
|
| 77 |
+
const visitorUser = {
|
| 78 |
+
name: 'Visitor',
|
| 79 |
+
email: email,
|
| 80 |
+
role: 'visitor'
|
| 81 |
+
};
|
| 82 |
+
const token = `visitor_${Date.now()}`;
|
| 83 |
+
res.json({
|
| 84 |
+
success: true,
|
| 85 |
+
token,
|
| 86 |
+
user: visitorUser
|
| 87 |
+
});
|
| 88 |
+
}
|
| 89 |
+
} catch (error) {
|
| 90 |
+
console.error('Login error:', error);
|
| 91 |
+
res.status(500).json({ error: 'Login failed' });
|
| 92 |
+
}
|
| 93 |
+
});
|
| 94 |
+
|
| 95 |
+
// Get user profile
|
| 96 |
+
router.get('/profile', authenticateToken, async (req, res) => {
|
| 97 |
+
try {
|
| 98 |
+
// For this simplified system, we'll return a basic profile
|
| 99 |
+
// The actual user data is stored in localStorage on the frontend
|
| 100 |
+
res.json({
|
| 101 |
+
success: true,
|
| 102 |
+
user: {
|
| 103 |
+
name: 'User',
|
| 104 |
+
email: 'user@example.com',
|
| 105 |
+
role: 'student'
|
| 106 |
+
}
|
| 107 |
+
});
|
| 108 |
+
} catch (error) {
|
| 109 |
+
console.error('Profile error:', error);
|
| 110 |
+
res.status(500).json({ error: 'Failed to get profile' });
|
| 111 |
+
}
|
| 112 |
+
});
|
| 113 |
+
|
| 114 |
+
// Admin endpoints
|
| 115 |
+
// Get all users (admin only)
|
| 116 |
+
router.get('/admin/users', authenticateToken, async (req, res) => {
|
| 117 |
+
try {
|
| 118 |
+
// In our simplified system, return predefined users
|
| 119 |
+
const users = Object.values(PREDEFINED_USERS);
|
| 120 |
+
res.json({
|
| 121 |
+
success: true,
|
| 122 |
+
users: users
|
| 123 |
+
});
|
| 124 |
+
} catch (error) {
|
| 125 |
+
console.error('Get users error:', error);
|
| 126 |
+
res.status(500).json({ error: 'Failed to get users' });
|
| 127 |
+
}
|
| 128 |
+
});
|
| 129 |
+
|
| 130 |
+
// Get system statistics (admin only)
|
| 131 |
+
router.get('/admin/stats', authenticateToken, async (req, res) => {
|
| 132 |
+
try {
|
| 133 |
+
// Import models for statistics
|
| 134 |
+
const SourceText = require('../models/SourceText');
|
| 135 |
+
const Submission = require('../models/Submission');
|
| 136 |
+
|
| 137 |
+
const stats = {
|
| 138 |
+
totalUsers: Object.keys(PREDEFINED_USERS).length,
|
| 139 |
+
practiceExamples: await SourceText.countDocuments({ sourceType: 'practice' }),
|
| 140 |
+
totalSubmissions: await Submission.countDocuments(),
|
| 141 |
+
activeSessions: 1 // Placeholder
|
| 142 |
+
};
|
| 143 |
+
|
| 144 |
+
res.json({
|
| 145 |
+
success: true,
|
| 146 |
+
stats: stats
|
| 147 |
+
});
|
| 148 |
+
} catch (error) {
|
| 149 |
+
console.error('Get stats error:', error);
|
| 150 |
+
res.status(500).json({ error: 'Failed to get statistics' });
|
| 151 |
+
}
|
| 152 |
+
});
|
| 153 |
+
|
| 154 |
+
// Get all practice examples (admin only)
|
| 155 |
+
router.get('/admin/practice-examples', authenticateToken, async (req, res) => {
|
| 156 |
+
try {
|
| 157 |
+
const SourceText = require('../models/SourceText');
|
| 158 |
+
const examples = await SourceText.find({ sourceType: 'practice' }).sort({ createdAt: -1 });
|
| 159 |
+
|
| 160 |
+
res.json({
|
| 161 |
+
success: true,
|
| 162 |
+
examples: examples
|
| 163 |
+
});
|
| 164 |
+
} catch (error) {
|
| 165 |
+
console.error('Get practice examples error:', error);
|
| 166 |
+
res.status(500).json({ error: 'Failed to get practice examples' });
|
| 167 |
+
}
|
| 168 |
+
});
|
| 169 |
+
|
| 170 |
+
// Add new practice example (admin only)
|
| 171 |
+
router.post('/admin/practice-examples', authenticateToken, async (req, res) => {
|
| 172 |
+
try {
|
| 173 |
+
const SourceText = require('../models/SourceText');
|
| 174 |
+
const { title, content, sourceLanguage, culturalElements, difficulty } = req.body;
|
| 175 |
+
|
| 176 |
+
const newExample = new SourceText({
|
| 177 |
+
title,
|
| 178 |
+
content,
|
| 179 |
+
sourceLanguage,
|
| 180 |
+
sourceType: 'practice',
|
| 181 |
+
culturalElements: culturalElements || [],
|
| 182 |
+
difficulty: difficulty || 'intermediate'
|
| 183 |
+
});
|
| 184 |
+
|
| 185 |
+
await newExample.save();
|
| 186 |
+
|
| 187 |
+
res.status(201).json({
|
| 188 |
+
success: true,
|
| 189 |
+
message: 'Practice example added successfully',
|
| 190 |
+
example: newExample
|
| 191 |
+
});
|
| 192 |
+
} catch (error) {
|
| 193 |
+
console.error('Add practice example error:', error);
|
| 194 |
+
res.status(500).json({ error: 'Failed to add practice example' });
|
| 195 |
+
}
|
| 196 |
+
});
|
| 197 |
+
|
| 198 |
+
// Update practice example (admin only)
|
| 199 |
+
router.put('/admin/practice-examples/:id', authenticateToken, async (req, res) => {
|
| 200 |
+
try {
|
| 201 |
+
const SourceText = require('../models/SourceText');
|
| 202 |
+
const { title, content, sourceLanguage, culturalElements, difficulty } = req.body;
|
| 203 |
+
|
| 204 |
+
const updatedExample = await SourceText.findByIdAndUpdate(
|
| 205 |
+
req.params.id,
|
| 206 |
+
{
|
| 207 |
+
title,
|
| 208 |
+
content,
|
| 209 |
+
sourceLanguage,
|
| 210 |
+
culturalElements: culturalElements || [],
|
| 211 |
+
difficulty: difficulty || 'intermediate'
|
| 212 |
+
},
|
| 213 |
+
{ new: true, runValidators: true }
|
| 214 |
+
);
|
| 215 |
+
|
| 216 |
+
if (!updatedExample) {
|
| 217 |
+
return res.status(404).json({ error: 'Practice example not found' });
|
| 218 |
+
}
|
| 219 |
+
|
| 220 |
+
res.json({
|
| 221 |
+
success: true,
|
| 222 |
+
message: 'Practice example updated successfully',
|
| 223 |
+
example: updatedExample
|
| 224 |
+
});
|
| 225 |
+
} catch (error) {
|
| 226 |
+
console.error('Update practice example error:', error);
|
| 227 |
+
res.status(500).json({ error: 'Failed to update practice example' });
|
| 228 |
+
}
|
| 229 |
+
});
|
| 230 |
+
|
| 231 |
+
// Delete practice example (admin only)
|
| 232 |
+
router.delete('/admin/practice-examples/:id', authenticateToken, async (req, res) => {
|
| 233 |
+
try {
|
| 234 |
+
const SourceText = require('../models/SourceText');
|
| 235 |
+
|
| 236 |
+
const deletedExample = await SourceText.findByIdAndDelete(req.params.id);
|
| 237 |
+
|
| 238 |
+
if (!deletedExample) {
|
| 239 |
+
return res.status(404).json({ error: 'Practice example not found' });
|
| 240 |
+
}
|
| 241 |
+
|
| 242 |
+
res.json({
|
| 243 |
+
success: true,
|
| 244 |
+
message: 'Practice example deleted successfully'
|
| 245 |
+
});
|
| 246 |
+
} catch (error) {
|
| 247 |
+
console.error('Delete practice example error:', error);
|
| 248 |
+
res.status(500).json({ error: 'Failed to delete practice example' });
|
| 249 |
+
}
|
| 250 |
+
});
|
| 251 |
+
|
| 252 |
+
// Add new user (admin only)
|
| 253 |
+
router.post('/admin/users', authenticateToken, async (req, res) => {
|
| 254 |
+
try {
|
| 255 |
+
const { name, email, role } = req.body;
|
| 256 |
+
|
| 257 |
+
// Validate required fields
|
| 258 |
+
if (!name || !email || !role) {
|
| 259 |
+
return res.status(400).json({ error: 'Name, email, and role are required' });
|
| 260 |
+
}
|
| 261 |
+
|
| 262 |
+
// Check if user already exists
|
| 263 |
+
if (PREDEFINED_USERS[email]) {
|
| 264 |
+
return res.status(400).json({ error: 'User with this email already exists' });
|
| 265 |
+
}
|
| 266 |
+
|
| 267 |
+
// Add to predefined users (in a real app, this would be saved to database)
|
| 268 |
+
PREDEFINED_USERS[email] = { name, email, role };
|
| 269 |
+
|
| 270 |
+
res.status(201).json({
|
| 271 |
+
success: true,
|
| 272 |
+
message: 'User added successfully',
|
| 273 |
+
user: { name, email, role }
|
| 274 |
+
});
|
| 275 |
+
} catch (error) {
|
| 276 |
+
console.error('Add user error:', error);
|
| 277 |
+
res.status(500).json({ error: 'Failed to add user' });
|
| 278 |
+
}
|
| 279 |
+
});
|
| 280 |
+
|
| 281 |
+
// Update user (admin only)
|
| 282 |
+
router.put('/admin/users/:email', authenticateToken, async (req, res) => {
|
| 283 |
+
try {
|
| 284 |
+
const { name, role } = req.body;
|
| 285 |
+
const email = req.params.email;
|
| 286 |
+
|
| 287 |
+
// Check if user exists
|
| 288 |
+
if (!PREDEFINED_USERS[email]) {
|
| 289 |
+
return res.status(404).json({ error: 'User not found' });
|
| 290 |
+
}
|
| 291 |
+
|
| 292 |
+
// Update user
|
| 293 |
+
PREDEFINED_USERS[email] = {
|
| 294 |
+
...PREDEFINED_USERS[email],
|
| 295 |
+
name: name || PREDEFINED_USERS[email].name,
|
| 296 |
+
role: role || PREDEFINED_USERS[email].role
|
| 297 |
+
};
|
| 298 |
+
|
| 299 |
+
res.json({
|
| 300 |
+
success: true,
|
| 301 |
+
message: 'User updated successfully',
|
| 302 |
+
user: PREDEFINED_USERS[email]
|
| 303 |
+
});
|
| 304 |
+
} catch (error) {
|
| 305 |
+
console.error('Update user error:', error);
|
| 306 |
+
res.status(500).json({ error: 'Failed to update user' });
|
| 307 |
+
}
|
| 308 |
+
});
|
| 309 |
+
|
| 310 |
+
// Delete user (admin only)
|
| 311 |
+
router.delete('/admin/users/:email', authenticateToken, async (req, res) => {
|
| 312 |
+
try {
|
| 313 |
+
const email = req.params.email;
|
| 314 |
+
|
| 315 |
+
// Check if user exists
|
| 316 |
+
if (!PREDEFINED_USERS[email]) {
|
| 317 |
+
return res.status(404).json({ error: 'User not found' });
|
| 318 |
+
}
|
| 319 |
+
|
| 320 |
+
// Prevent deleting the admin user
|
| 321 |
+
if (email === 'hongchang.yu@monash.edu') {
|
| 322 |
+
return res.status(400).json({ error: 'Cannot delete the main admin user' });
|
| 323 |
+
}
|
| 324 |
+
|
| 325 |
+
// Delete user
|
| 326 |
+
delete PREDEFINED_USERS[email];
|
| 327 |
+
|
| 328 |
+
res.json({
|
| 329 |
+
success: true,
|
| 330 |
+
message: 'User deleted successfully'
|
| 331 |
+
});
|
| 332 |
+
} catch (error) {
|
| 333 |
+
console.error('Delete user error:', error);
|
| 334 |
+
res.status(500).json({ error: 'Failed to delete user' });
|
| 335 |
+
}
|
| 336 |
+
});
|
| 337 |
+
|
| 338 |
+
// ===== TUTORIAL TASKS MANAGEMENT =====
|
| 339 |
+
|
| 340 |
+
// Get all tutorial tasks (admin only)
|
| 341 |
+
router.get('/admin/tutorial-tasks', authenticateToken, async (req, res) => {
|
| 342 |
+
try {
|
| 343 |
+
const SourceText = require('../models/SourceText');
|
| 344 |
+
|
| 345 |
+
const tutorialTasks = await SourceText.find({ category: 'tutorial' })
|
| 346 |
+
.sort({ weekNumber: 1, createdAt: -1 });
|
| 347 |
+
|
| 348 |
+
res.json({
|
| 349 |
+
success: true,
|
| 350 |
+
tutorialTasks
|
| 351 |
+
});
|
| 352 |
+
} catch (error) {
|
| 353 |
+
console.error('Get tutorial tasks error:', error);
|
| 354 |
+
res.status(500).json({ error: 'Failed to get tutorial tasks' });
|
| 355 |
+
}
|
| 356 |
+
});
|
| 357 |
+
|
| 358 |
+
// Add new tutorial task (admin only)
|
| 359 |
+
router.post('/admin/tutorial-tasks', authenticateToken, async (req, res) => {
|
| 360 |
+
try {
|
| 361 |
+
const SourceText = require('../models/SourceText');
|
| 362 |
+
const { title, content, sourceLanguage, sourceCulture, weekNumber, difficulty, culturalElements } = req.body;
|
| 363 |
+
|
| 364 |
+
// Validate required fields
|
| 365 |
+
if (!title || !content || !sourceLanguage || !weekNumber) {
|
| 366 |
+
return res.status(400).json({ error: 'Title, content, sourceLanguage, and weekNumber are required' });
|
| 367 |
+
}
|
| 368 |
+
|
| 369 |
+
const newTutorialTask = new SourceText({
|
| 370 |
+
title,
|
| 371 |
+
content,
|
| 372 |
+
sourceLanguage,
|
| 373 |
+
category: 'tutorial',
|
| 374 |
+
weekNumber: parseInt(weekNumber),
|
| 375 |
+
difficulty: difficulty || 'intermediate',
|
| 376 |
+
culturalElements: culturalElements || [],
|
| 377 |
+
sourceType: 'tutorial'
|
| 378 |
+
});
|
| 379 |
+
|
| 380 |
+
const savedTask = await newTutorialTask.save();
|
| 381 |
+
|
| 382 |
+
res.status(201).json({
|
| 383 |
+
success: true,
|
| 384 |
+
message: 'Tutorial task added successfully',
|
| 385 |
+
tutorialTask: savedTask
|
| 386 |
+
});
|
| 387 |
+
} catch (error) {
|
| 388 |
+
console.error('Add tutorial task error:', error);
|
| 389 |
+
res.status(500).json({ error: 'Failed to add tutorial task' });
|
| 390 |
+
}
|
| 391 |
+
});
|
| 392 |
+
|
| 393 |
+
// Update tutorial task (admin only)
|
| 394 |
+
router.put('/admin/tutorial-tasks/:id', authenticateToken, requireAdmin, async (req, res) => {
|
| 395 |
+
try {
|
| 396 |
+
const SourceText = require('../models/SourceText');
|
| 397 |
+
const { title, content, sourceLanguage, sourceCulture, weekNumber, difficulty, culturalElements } = req.body;
|
| 398 |
+
|
| 399 |
+
const updatedTask = await SourceText.findByIdAndUpdate(
|
| 400 |
+
req.params.id,
|
| 401 |
+
{
|
| 402 |
+
title,
|
| 403 |
+
content,
|
| 404 |
+
sourceLanguage,
|
| 405 |
+
weekNumber: parseInt(weekNumber),
|
| 406 |
+
difficulty: difficulty || 'intermediate',
|
| 407 |
+
culturalElements: culturalElements || []
|
| 408 |
+
},
|
| 409 |
+
{ new: true, runValidators: true }
|
| 410 |
+
);
|
| 411 |
+
|
| 412 |
+
if (!updatedTask) {
|
| 413 |
+
return res.status(404).json({ error: 'Tutorial task not found' });
|
| 414 |
+
}
|
| 415 |
+
|
| 416 |
+
res.json({
|
| 417 |
+
success: true,
|
| 418 |
+
message: 'Tutorial task updated successfully',
|
| 419 |
+
tutorialTask: updatedTask
|
| 420 |
+
});
|
| 421 |
+
} catch (error) {
|
| 422 |
+
console.error('Update tutorial task error:', error);
|
| 423 |
+
res.status(500).json({ error: 'Failed to update tutorial task' });
|
| 424 |
+
}
|
| 425 |
+
});
|
| 426 |
+
|
| 427 |
+
// Delete tutorial task (admin only)
|
| 428 |
+
router.delete('/admin/tutorial-tasks/:id', authenticateToken, async (req, res) => {
|
| 429 |
+
try {
|
| 430 |
+
const SourceText = require('../models/SourceText');
|
| 431 |
+
|
| 432 |
+
const deletedTask = await SourceText.findByIdAndDelete(req.params.id);
|
| 433 |
+
|
| 434 |
+
if (!deletedTask) {
|
| 435 |
+
return res.status(404).json({ error: 'Tutorial task not found' });
|
| 436 |
+
}
|
| 437 |
+
|
| 438 |
+
res.json({
|
| 439 |
+
success: true,
|
| 440 |
+
message: 'Tutorial task deleted successfully'
|
| 441 |
+
});
|
| 442 |
+
} catch (error) {
|
| 443 |
+
console.error('Delete tutorial task error:', error);
|
| 444 |
+
res.status(500).json({ error: 'Failed to delete tutorial task' });
|
| 445 |
+
}
|
| 446 |
+
});
|
| 447 |
+
|
| 448 |
+
// ===== WEEKLY PRACTICE MANAGEMENT =====
|
| 449 |
+
|
| 450 |
+
// Get all weekly practice tasks (admin only)
|
| 451 |
+
router.get('/admin/weekly-practice', authenticateToken, async (req, res) => {
|
| 452 |
+
try {
|
| 453 |
+
const SourceText = require('../models/SourceText');
|
| 454 |
+
|
| 455 |
+
const weeklyPractice = await SourceText.find({ category: 'weekly-practice' })
|
| 456 |
+
.sort({ weekNumber: 1, createdAt: -1 });
|
| 457 |
+
|
| 458 |
+
res.json({
|
| 459 |
+
success: true,
|
| 460 |
+
weeklyPractice
|
| 461 |
+
});
|
| 462 |
+
} catch (error) {
|
| 463 |
+
console.error('Get weekly practice error:', error);
|
| 464 |
+
res.status(500).json({ error: 'Failed to get weekly practice' });
|
| 465 |
+
}
|
| 466 |
+
});
|
| 467 |
+
|
| 468 |
+
// Add new weekly practice task (admin only)
|
| 469 |
+
router.post('/admin/weekly-practice', authenticateToken, async (req, res) => {
|
| 470 |
+
try {
|
| 471 |
+
const SourceText = require('../models/SourceText');
|
| 472 |
+
const { title, content, sourceLanguage, sourceCulture, weekNumber, difficulty, culturalElements } = req.body;
|
| 473 |
+
|
| 474 |
+
// Validate required fields
|
| 475 |
+
if (!title || !content || !sourceLanguage || !weekNumber) {
|
| 476 |
+
return res.status(400).json({ error: 'Title, content, sourceLanguage, and weekNumber are required' });
|
| 477 |
+
}
|
| 478 |
+
|
| 479 |
+
const newWeeklyPractice = new SourceText({
|
| 480 |
+
title,
|
| 481 |
+
content,
|
| 482 |
+
sourceLanguage,
|
| 483 |
+
category: 'weekly-practice',
|
| 484 |
+
weekNumber: parseInt(weekNumber),
|
| 485 |
+
difficulty: difficulty || 'intermediate',
|
| 486 |
+
culturalElements: culturalElements || [],
|
| 487 |
+
sourceType: 'weekly-practice'
|
| 488 |
+
});
|
| 489 |
+
|
| 490 |
+
const savedPractice = await newWeeklyPractice.save();
|
| 491 |
+
|
| 492 |
+
res.status(201).json({
|
| 493 |
+
success: true,
|
| 494 |
+
message: 'Weekly practice added successfully',
|
| 495 |
+
weeklyPractice: savedPractice
|
| 496 |
+
});
|
| 497 |
+
} catch (error) {
|
| 498 |
+
console.error('Add weekly practice error:', error);
|
| 499 |
+
res.status(500).json({ error: 'Failed to add weekly practice' });
|
| 500 |
+
}
|
| 501 |
+
});
|
| 502 |
+
|
| 503 |
+
// Create weekly practice task (admin only)
|
| 504 |
+
router.post('/admin/weekly-practice', authenticateToken, requireAdmin, async (req, res) => {
|
| 505 |
+
try {
|
| 506 |
+
const SourceText = require('../models/SourceText');
|
| 507 |
+
const { content, weekNumber, category } = req.body;
|
| 508 |
+
|
| 509 |
+
// Validate required fields
|
| 510 |
+
if (!content || !weekNumber) {
|
| 511 |
+
return res.status(400).json({ error: 'Content and week number are required' });
|
| 512 |
+
}
|
| 513 |
+
|
| 514 |
+
const newPractice = new SourceText({
|
| 515 |
+
content,
|
| 516 |
+
weekNumber: parseInt(weekNumber),
|
| 517 |
+
category: category || 'weekly-practice',
|
| 518 |
+
title: `Weekly Practice Week ${weekNumber}`,
|
| 519 |
+
sourceLanguage: 'English',
|
| 520 |
+
sourceType: 'weekly-practice'
|
| 521 |
+
});
|
| 522 |
+
|
| 523 |
+
const savedPractice = await newPractice.save();
|
| 524 |
+
|
| 525 |
+
res.status(201).json({
|
| 526 |
+
success: true,
|
| 527 |
+
message: 'Weekly practice created successfully',
|
| 528 |
+
practice: savedPractice
|
| 529 |
+
});
|
| 530 |
+
} catch (error) {
|
| 531 |
+
console.error('Create weekly practice error:', error);
|
| 532 |
+
res.status(500).json({ error: 'Failed to create weekly practice' });
|
| 533 |
+
}
|
| 534 |
+
});
|
| 535 |
+
|
| 536 |
+
// Update weekly practice task (admin only)
|
| 537 |
+
router.put('/admin/weekly-practice/:id', authenticateToken, requireAdmin, async (req, res) => {
|
| 538 |
+
try {
|
| 539 |
+
const SourceText = require('../models/SourceText');
|
| 540 |
+
const { title, content, sourceLanguage, sourceCulture, weekNumber, difficulty, culturalElements } = req.body;
|
| 541 |
+
|
| 542 |
+
const updatedPractice = await SourceText.findByIdAndUpdate(
|
| 543 |
+
req.params.id,
|
| 544 |
+
{
|
| 545 |
+
title,
|
| 546 |
+
content,
|
| 547 |
+
sourceLanguage,
|
| 548 |
+
weekNumber: parseInt(weekNumber),
|
| 549 |
+
difficulty: difficulty || 'intermediate',
|
| 550 |
+
culturalElements: culturalElements || []
|
| 551 |
+
},
|
| 552 |
+
{ new: true, runValidators: true }
|
| 553 |
+
);
|
| 554 |
+
|
| 555 |
+
if (!updatedPractice) {
|
| 556 |
+
return res.status(404).json({ error: 'Weekly practice not found' });
|
| 557 |
+
}
|
| 558 |
+
|
| 559 |
+
res.json({
|
| 560 |
+
success: true,
|
| 561 |
+
message: 'Weekly practice updated successfully',
|
| 562 |
+
weeklyPractice: updatedPractice
|
| 563 |
+
});
|
| 564 |
+
} catch (error) {
|
| 565 |
+
console.error('Update weekly practice error:', error);
|
| 566 |
+
res.status(500).json({ error: 'Failed to update weekly practice' });
|
| 567 |
+
}
|
| 568 |
+
});
|
| 569 |
+
|
| 570 |
+
// Delete weekly practice task (admin only)
|
| 571 |
+
router.delete('/admin/weekly-practice/:id', authenticateToken, requireAdmin, async (req, res) => {
|
| 572 |
+
try {
|
| 573 |
+
const SourceText = require('../models/SourceText');
|
| 574 |
+
|
| 575 |
+
const deletedPractice = await SourceText.findByIdAndDelete(req.params.id);
|
| 576 |
+
|
| 577 |
+
if (!deletedPractice) {
|
| 578 |
+
return res.status(404).json({ error: 'Weekly practice not found' });
|
| 579 |
+
}
|
| 580 |
+
|
| 581 |
+
res.json({
|
| 582 |
+
success: true,
|
| 583 |
+
message: 'Weekly practice deleted successfully'
|
| 584 |
+
});
|
| 585 |
+
} catch (error) {
|
| 586 |
+
console.error('Delete weekly practice error:', error);
|
| 587 |
+
res.status(500).json({ error: 'Failed to delete weekly practice' });
|
| 588 |
+
}
|
| 589 |
+
});
|
| 590 |
+
|
| 591 |
+
// Create tutorial task (admin only)
|
| 592 |
+
router.post('/admin/tutorial-tasks', authenticateToken, requireAdmin, async (req, res) => {
|
| 593 |
+
try {
|
| 594 |
+
const SourceText = require('../models/SourceText');
|
| 595 |
+
const { content, weekNumber, category } = req.body;
|
| 596 |
+
|
| 597 |
+
// Validate required fields
|
| 598 |
+
if (!content || !weekNumber) {
|
| 599 |
+
return res.status(400).json({ error: 'Content and week number are required' });
|
| 600 |
+
}
|
| 601 |
+
|
| 602 |
+
const newTask = new SourceText({
|
| 603 |
+
content,
|
| 604 |
+
weekNumber: parseInt(weekNumber),
|
| 605 |
+
category: category || 'tutorial',
|
| 606 |
+
title: `Tutorial Task Week ${weekNumber}`,
|
| 607 |
+
sourceLanguage: 'English',
|
| 608 |
+
sourceType: 'tutorial'
|
| 609 |
+
});
|
| 610 |
+
|
| 611 |
+
const savedTask = await newTask.save();
|
| 612 |
+
|
| 613 |
+
res.status(201).json({
|
| 614 |
+
success: true,
|
| 615 |
+
message: 'Tutorial task created successfully',
|
| 616 |
+
task: savedTask
|
| 617 |
+
});
|
| 618 |
+
} catch (error) {
|
| 619 |
+
console.error('Create tutorial task error:', error);
|
| 620 |
+
res.status(500).json({ error: 'Failed to create tutorial task' });
|
| 621 |
+
}
|
| 622 |
+
});
|
| 623 |
+
|
| 624 |
+
// Update tutorial task (admin only)
|
| 625 |
+
router.put('/admin/tutorial-tasks/:id', authenticateToken, requireAdmin, async (req, res) => {
|
| 626 |
+
try {
|
| 627 |
+
const SourceText = require('../models/SourceText');
|
| 628 |
+
const { content, translationBrief, weekNumber } = req.body;
|
| 629 |
+
|
| 630 |
+
const updatedTask = await SourceText.findByIdAndUpdate(
|
| 631 |
+
req.params.id,
|
| 632 |
+
{
|
| 633 |
+
content,
|
| 634 |
+
translationBrief,
|
| 635 |
+
weekNumber: parseInt(weekNumber)
|
| 636 |
+
},
|
| 637 |
+
{ new: true, runValidators: true }
|
| 638 |
+
);
|
| 639 |
+
|
| 640 |
+
if (!updatedTask) {
|
| 641 |
+
return res.status(404).json({ error: 'Tutorial task not found' });
|
| 642 |
+
}
|
| 643 |
+
|
| 644 |
+
res.json({
|
| 645 |
+
success: true,
|
| 646 |
+
message: 'Tutorial task updated successfully',
|
| 647 |
+
task: updatedTask
|
| 648 |
+
});
|
| 649 |
+
} catch (error) {
|
| 650 |
+
console.error('Update tutorial task error:', error);
|
| 651 |
+
res.status(500).json({ error: 'Failed to update tutorial task' });
|
| 652 |
+
}
|
| 653 |
+
});
|
| 654 |
+
|
| 655 |
+
// Delete tutorial task (admin only)
|
| 656 |
+
router.delete('/admin/tutorial-tasks/:id', authenticateToken, requireAdmin, async (req, res) => {
|
| 657 |
+
try {
|
| 658 |
+
const SourceText = require('../models/SourceText');
|
| 659 |
+
|
| 660 |
+
const deletedTask = await SourceText.findByIdAndDelete(req.params.id);
|
| 661 |
+
|
| 662 |
+
if (!deletedTask) {
|
| 663 |
+
return res.status(404).json({ error: 'Tutorial task not found' });
|
| 664 |
+
}
|
| 665 |
+
|
| 666 |
+
res.json({
|
| 667 |
+
success: true,
|
| 668 |
+
message: 'Tutorial task deleted successfully'
|
| 669 |
+
});
|
| 670 |
+
} catch (error) {
|
| 671 |
+
console.error('Delete tutorial task error:', error);
|
| 672 |
+
res.status(500).json({ error: 'Failed to delete tutorial task' });
|
| 673 |
+
}
|
| 674 |
+
});
|
| 675 |
+
|
| 676 |
+
// Add translation brief (admin only)
|
| 677 |
+
router.post('/admin/translation-brief', authenticateToken, requireAdmin, async (req, res) => {
|
| 678 |
+
try {
|
| 679 |
+
const SourceText = require('../models/SourceText');
|
| 680 |
+
const { weekNumber, translationBrief, type } = req.body;
|
| 681 |
+
|
| 682 |
+
// Validate required fields
|
| 683 |
+
if (!weekNumber || !translationBrief || !type) {
|
| 684 |
+
return res.status(400).json({ error: 'Week number, translation brief, and type are required' });
|
| 685 |
+
}
|
| 686 |
+
|
| 687 |
+
// Update all existing tasks of the specified type and week with the translation brief
|
| 688 |
+
const result = await SourceText.updateMany(
|
| 689 |
+
{
|
| 690 |
+
category: type === 'tutorial' ? 'tutorial' : 'weekly-practice',
|
| 691 |
+
weekNumber: parseInt(weekNumber)
|
| 692 |
+
},
|
| 693 |
+
{ translationBrief }
|
| 694 |
+
);
|
| 695 |
+
|
| 696 |
+
if (result.modifiedCount === 0) {
|
| 697 |
+
return res.status(404).json({ error: 'No tasks found for the specified week and type' });
|
| 698 |
+
}
|
| 699 |
+
|
| 700 |
+
res.json({
|
| 701 |
+
success: true,
|
| 702 |
+
message: `Translation brief added successfully to ${result.modifiedCount} tasks`,
|
| 703 |
+
modifiedCount: result.modifiedCount
|
| 704 |
+
});
|
| 705 |
+
} catch (error) {
|
| 706 |
+
console.error('Add translation brief error:', error);
|
| 707 |
+
res.status(500).json({ error: 'Failed to add translation brief' });
|
| 708 |
+
}
|
| 709 |
+
});
|
| 710 |
+
|
| 711 |
+
// Update tutorial brief (admin only)
|
| 712 |
+
router.put('/admin/tutorial-brief/:weekNumber', authenticateToken, requireAdmin, async (req, res) => {
|
| 713 |
+
try {
|
| 714 |
+
const SourceText = require('../models/SourceText');
|
| 715 |
+
const { translationBrief } = req.body;
|
| 716 |
+
const weekNumber = parseInt(req.params.weekNumber);
|
| 717 |
+
|
| 718 |
+
// Validate required fields - allow empty string for removal
|
| 719 |
+
if (translationBrief === undefined || translationBrief === null) {
|
| 720 |
+
return res.status(400).json({ error: 'Translation brief is required' });
|
| 721 |
+
}
|
| 722 |
+
|
| 723 |
+
// Update all tutorial tasks for the specified week with the new translation brief
|
| 724 |
+
const result = await SourceText.updateMany(
|
| 725 |
+
{
|
| 726 |
+
category: 'tutorial',
|
| 727 |
+
weekNumber: weekNumber
|
| 728 |
+
},
|
| 729 |
+
{ translationBrief }
|
| 730 |
+
);
|
| 731 |
+
|
| 732 |
+
if (result.modifiedCount === 0) {
|
| 733 |
+
return res.status(404).json({ error: 'No tutorial tasks found for the specified week' });
|
| 734 |
+
}
|
| 735 |
+
|
| 736 |
+
res.json({
|
| 737 |
+
success: true,
|
| 738 |
+
message: `Translation brief updated successfully for ${result.modifiedCount} tutorial tasks`,
|
| 739 |
+
modifiedCount: result.modifiedCount
|
| 740 |
+
});
|
| 741 |
+
} catch (error) {
|
| 742 |
+
console.error('Update tutorial brief error:', error);
|
| 743 |
+
res.status(500).json({ error: 'Failed to update translation brief' });
|
| 744 |
+
}
|
| 745 |
+
});
|
| 746 |
+
|
| 747 |
+
// Update weekly practice brief (admin only)
|
| 748 |
+
router.put('/admin/weekly-brief/:weekNumber', authenticateToken, requireAdmin, async (req, res) => {
|
| 749 |
+
try {
|
| 750 |
+
const SourceText = require('../models/SourceText');
|
| 751 |
+
const { translationBrief } = req.body;
|
| 752 |
+
const weekNumber = parseInt(req.params.weekNumber);
|
| 753 |
+
|
| 754 |
+
// Validate required fields - allow empty string for removal
|
| 755 |
+
if (translationBrief === undefined || translationBrief === null) {
|
| 756 |
+
return res.status(400).json({ error: 'Translation brief is required' });
|
| 757 |
+
}
|
| 758 |
+
|
| 759 |
+
// Update all weekly practice tasks for the specified week with the new translation brief
|
| 760 |
+
const result = await SourceText.updateMany(
|
| 761 |
+
{
|
| 762 |
+
category: 'weekly-practice',
|
| 763 |
+
weekNumber: weekNumber
|
| 764 |
+
},
|
| 765 |
+
{ translationBrief }
|
| 766 |
+
);
|
| 767 |
+
|
| 768 |
+
if (result.modifiedCount === 0) {
|
| 769 |
+
return res.status(404).json({ error: 'No weekly practice tasks found for the specified week' });
|
| 770 |
+
}
|
| 771 |
+
|
| 772 |
+
res.json({
|
| 773 |
+
success: true,
|
| 774 |
+
message: `Translation brief updated successfully for ${result.modifiedCount} weekly practice tasks`,
|
| 775 |
+
modifiedCount: result.modifiedCount
|
| 776 |
+
});
|
| 777 |
+
} catch (error) {
|
| 778 |
+
console.error('Update weekly practice brief error:', error);
|
| 779 |
+
res.status(500).json({ error: 'Failed to update translation brief' });
|
| 780 |
+
}
|
| 781 |
+
});
|
| 782 |
+
|
| 783 |
+
module.exports = { router, authenticateToken };
|
routes/search.js
ADDED
|
@@ -0,0 +1,206 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
const express = require('express');
|
| 2 |
+
const router = express.Router();
|
| 3 |
+
const { authenticateToken } = require('./auth');
|
| 4 |
+
|
| 5 |
+
// Get practice examples (now weekly practice for week 1)
|
| 6 |
+
router.get('/practice-examples', authenticateToken, async (req, res) => {
|
| 7 |
+
try {
|
| 8 |
+
const SourceText = require('../models/SourceText');
|
| 9 |
+
const examples = await SourceText.find({
|
| 10 |
+
category: 'weekly-practice',
|
| 11 |
+
weekNumber: 1
|
| 12 |
+
}).sort({ createdAt: 1 });
|
| 13 |
+
|
| 14 |
+
res.json(examples);
|
| 15 |
+
} catch (error) {
|
| 16 |
+
console.error('Get practice examples error:', error);
|
| 17 |
+
res.status(500).json({ error: 'Failed to get practice examples' });
|
| 18 |
+
}
|
| 19 |
+
});
|
| 20 |
+
|
| 21 |
+
// Get tutorial tasks by week
|
| 22 |
+
router.get('/tutorial-tasks/:week', authenticateToken, async (req, res) => {
|
| 23 |
+
try {
|
| 24 |
+
const SourceText = require('../models/SourceText');
|
| 25 |
+
const weekNumber = parseInt(req.params.week);
|
| 26 |
+
|
| 27 |
+
const tasks = await SourceText.find({
|
| 28 |
+
category: 'tutorial',
|
| 29 |
+
weekNumber: weekNumber
|
| 30 |
+
}).sort({ title: 1 });
|
| 31 |
+
|
| 32 |
+
res.json(tasks);
|
| 33 |
+
} catch (error) {
|
| 34 |
+
console.error('Get tutorial tasks error:', error);
|
| 35 |
+
res.status(500).json({ error: 'Failed to get tutorial tasks' });
|
| 36 |
+
}
|
| 37 |
+
});
|
| 38 |
+
|
| 39 |
+
// Get weekly practice by week
|
| 40 |
+
router.get('/weekly-practice/:week', authenticateToken, async (req, res) => {
|
| 41 |
+
try {
|
| 42 |
+
const SourceText = require('../models/SourceText');
|
| 43 |
+
const weekNumber = parseInt(req.params.week);
|
| 44 |
+
|
| 45 |
+
const practice = await SourceText.find({
|
| 46 |
+
category: 'weekly-practice',
|
| 47 |
+
weekNumber: weekNumber
|
| 48 |
+
}).sort({ title: 1 });
|
| 49 |
+
|
| 50 |
+
res.json(practice);
|
| 51 |
+
} catch (error) {
|
| 52 |
+
console.error('Get weekly practice error:', error);
|
| 53 |
+
res.status(500).json({ error: 'Failed to get weekly practice' });
|
| 54 |
+
}
|
| 55 |
+
});
|
| 56 |
+
|
| 57 |
+
// Initialize practice examples (convert to weekly practice week 1)
|
| 58 |
+
router.post('/initialize-practice-examples', authenticateToken, async (req, res) => {
|
| 59 |
+
try {
|
| 60 |
+
const SourceText = require('../models/SourceText');
|
| 61 |
+
|
| 62 |
+
// Clear existing practice examples
|
| 63 |
+
await SourceText.deleteMany({ category: 'weekly-practice', weekNumber: 1 });
|
| 64 |
+
|
| 65 |
+
const practiceExamples = [
|
| 66 |
+
{
|
| 67 |
+
content: '为什么睡前一定要吃夜宵?因为这样才不会做饿梦。',
|
| 68 |
+
category: 'weekly-practice',
|
| 69 |
+
weekNumber: 1
|
| 70 |
+
},
|
| 71 |
+
{
|
| 72 |
+
content: '女娲用什么补天?强扭的瓜。',
|
| 73 |
+
category: 'weekly-practice',
|
| 74 |
+
weekNumber: 1
|
| 75 |
+
},
|
| 76 |
+
{
|
| 77 |
+
content: '你知道如何区分真假大象吗?把他们仍进水中,真相会浮出水面的。',
|
| 78 |
+
category: 'weekly-practice',
|
| 79 |
+
weekNumber: 1
|
| 80 |
+
},
|
| 81 |
+
{
|
| 82 |
+
content: 'Why do we drive on a parkway and park on a driveway?',
|
| 83 |
+
category: 'weekly-practice',
|
| 84 |
+
weekNumber: 1
|
| 85 |
+
},
|
| 86 |
+
{
|
| 87 |
+
content: 'I can\'t believe I got fired from the calendar factory. All I did was take a day off.',
|
| 88 |
+
category: 'weekly-practice',
|
| 89 |
+
weekNumber: 1
|
| 90 |
+
},
|
| 91 |
+
{
|
| 92 |
+
content: 'Most people are shocked when they find out how bad I am as an electrician.',
|
| 93 |
+
category: 'weekly-practice',
|
| 94 |
+
weekNumber: 1
|
| 95 |
+
}
|
| 96 |
+
];
|
| 97 |
+
|
| 98 |
+
await SourceText.insertMany(practiceExamples);
|
| 99 |
+
|
| 100 |
+
res.json({
|
| 101 |
+
success: true,
|
| 102 |
+
message: 'Practice examples initialized successfully',
|
| 103 |
+
count: practiceExamples.length
|
| 104 |
+
});
|
| 105 |
+
} catch (error) {
|
| 106 |
+
console.error('Initialize practice examples error:', error);
|
| 107 |
+
res.status(500).json({ error: 'Failed to initialize practice examples' });
|
| 108 |
+
}
|
| 109 |
+
});
|
| 110 |
+
|
| 111 |
+
// Initialize tutorial tasks for a specific week
|
| 112 |
+
router.post('/initialize-tutorial-tasks/:week', authenticateToken, async (req, res) => {
|
| 113 |
+
try {
|
| 114 |
+
const SourceText = require('../models/SourceText');
|
| 115 |
+
const weekNumber = parseInt(req.params.week);
|
| 116 |
+
|
| 117 |
+
// Clear existing tutorial tasks for this week
|
| 118 |
+
await SourceText.deleteMany({ category: 'tutorial', weekNumber: weekNumber });
|
| 119 |
+
|
| 120 |
+
// Example tutorial tasks (you can customize these)
|
| 121 |
+
const tutorialTasks = [
|
| 122 |
+
{
|
| 123 |
+
title: `Tutorial Task 1 - Week ${weekNumber}`,
|
| 124 |
+
content: 'The first paragraph of the source text introduces the main concept and sets the context for the entire piece. This section establishes the foundation upon which the rest of the text builds.',
|
| 125 |
+
category: 'tutorial',
|
| 126 |
+
weekNumber: weekNumber,
|
| 127 |
+
sourceLanguage: 'English',
|
| 128 |
+
sourceCulture: 'Western',
|
| 129 |
+
translationBrief: weekNumber === 1 ? 'The municipal government of Xiamen plans to launch an international campaign to attract foreign investors and tourists. As part of the campaign, they\'re commissioning an English translation of a Chinese booklet about the city\'s history of foreign trade and cultural exchange, in addition to its tourist attractions. You\'ve been provided with the following excerpt for a test translation. They are aware that certain elements in the source text might not work well when translated into English for promotional purposes. So you\'re given the license to make changes where appropriate, but a list of such changes should be provided for the reference of the commissioner.' : 'Translate this text while maintaining the original tone and style. Pay attention to cultural nuances and ensure the translation is appropriate for the target audience. Focus on conveying the meaning accurately while preserving the author\'s intent.'
|
| 130 |
+
},
|
| 131 |
+
{
|
| 132 |
+
title: `Tutorial Task 2 - Week ${weekNumber}`,
|
| 133 |
+
content: 'The second paragraph develops the argument further, providing supporting evidence and examples that reinforce the main points established in the opening section.',
|
| 134 |
+
category: 'tutorial',
|
| 135 |
+
weekNumber: weekNumber,
|
| 136 |
+
sourceLanguage: 'English',
|
| 137 |
+
sourceCulture: 'Western',
|
| 138 |
+
translationBrief: weekNumber === 1 ? 'The municipal government of Xiamen plans to launch an international campaign to attract foreign investors and tourists. As part of the campaign, they\'re commissioning an English translation of a Chinese booklet about the city\'s history of foreign trade and cultural exchange, in addition to its tourist attractions. You\'ve been provided with the following excerpt for a test translation. They are aware that certain elements in the source text might not work well when translated into English for promotional purposes. So you\'re given the license to make changes where appropriate, but a list of such changes should be provided for the reference of the commissioner.' : 'Translate this text while maintaining the original tone and style. Pay attention to cultural nuances and ensure the translation is appropriate for the target audience. Focus on conveying the meaning accurately while preserving the author\'s intent.'
|
| 139 |
+
},
|
| 140 |
+
{
|
| 141 |
+
title: `Tutorial Task 3 - Week ${weekNumber}`,
|
| 142 |
+
content: 'The concluding paragraph brings together all the key elements discussed throughout the text, offering a synthesis of the main ideas and leaving the reader with a clear understanding of the central message.',
|
| 143 |
+
category: 'tutorial',
|
| 144 |
+
weekNumber: weekNumber,
|
| 145 |
+
sourceLanguage: 'English',
|
| 146 |
+
sourceCulture: 'Western',
|
| 147 |
+
translationBrief: weekNumber === 1 ? 'The municipal government of Xiamen plans to launch an international campaign to attract foreign investors and tourists. As part of the campaign, they\'re commissioning an English translation of a Chinese booklet about the city\'s history of foreign trade and cultural exchange, in addition to its tourist attractions. You\'ve been provided with the following excerpt for a test translation. They are aware that certain elements in the source text might not work well when translated into English for promotional purposes. So you\'re given the license to make changes where appropriate, but a list of such changes should be provided for the reference of the commissioner.' : 'Translate this text while maintaining the original tone and style. Pay attention to cultural nuances and ensure the translation is appropriate for the target audience. Focus on conveying the meaning accurately while preserving the author\'s intent.'
|
| 148 |
+
}
|
| 149 |
+
];
|
| 150 |
+
|
| 151 |
+
await SourceText.insertMany(tutorialTasks);
|
| 152 |
+
|
| 153 |
+
res.json({
|
| 154 |
+
success: true,
|
| 155 |
+
message: `Tutorial tasks for week ${weekNumber} initialized successfully`,
|
| 156 |
+
count: tutorialTasks.length
|
| 157 |
+
});
|
| 158 |
+
} catch (error) {
|
| 159 |
+
console.error('Initialize tutorial tasks error:', error);
|
| 160 |
+
res.status(500).json({ error: 'Failed to initialize tutorial tasks' });
|
| 161 |
+
}
|
| 162 |
+
});
|
| 163 |
+
|
| 164 |
+
// Initialize weekly practice for a specific week
|
| 165 |
+
router.post('/initialize-weekly-practice/:week', authenticateToken, async (req, res) => {
|
| 166 |
+
try {
|
| 167 |
+
const SourceText = require('../models/SourceText');
|
| 168 |
+
const weekNumber = parseInt(req.params.week);
|
| 169 |
+
|
| 170 |
+
// Clear existing weekly practice for this week
|
| 171 |
+
await SourceText.deleteMany({ category: 'weekly-practice', weekNumber: weekNumber });
|
| 172 |
+
|
| 173 |
+
// Example weekly practice (you can customize these)
|
| 174 |
+
const weeklyPractice = [
|
| 175 |
+
{
|
| 176 |
+
title: `Weekly Practice 1 - Week ${weekNumber}`,
|
| 177 |
+
content: 'This is a sample weekly practice example for week ' + weekNumber + '.',
|
| 178 |
+
category: 'weekly-practice',
|
| 179 |
+
weekNumber: weekNumber,
|
| 180 |
+
sourceLanguage: 'English',
|
| 181 |
+
sourceCulture: 'Western'
|
| 182 |
+
},
|
| 183 |
+
{
|
| 184 |
+
title: `Weekly Practice 2 - Week ${weekNumber}`,
|
| 185 |
+
content: 'Another sample weekly practice example for week ' + weekNumber + '.',
|
| 186 |
+
category: 'weekly-practice',
|
| 187 |
+
weekNumber: weekNumber,
|
| 188 |
+
sourceLanguage: 'English',
|
| 189 |
+
sourceCulture: 'Western'
|
| 190 |
+
}
|
| 191 |
+
];
|
| 192 |
+
|
| 193 |
+
await SourceText.insertMany(weeklyPractice);
|
| 194 |
+
|
| 195 |
+
res.json({
|
| 196 |
+
success: true,
|
| 197 |
+
message: `Weekly practice for week ${weekNumber} initialized successfully`,
|
| 198 |
+
count: weeklyPractice.length
|
| 199 |
+
});
|
| 200 |
+
} catch (error) {
|
| 201 |
+
console.error('Initialize weekly practice error:', error);
|
| 202 |
+
res.status(500).json({ error: 'Failed to initialize weekly practice' });
|
| 203 |
+
}
|
| 204 |
+
});
|
| 205 |
+
|
| 206 |
+
module.exports = router;
|
routes/sourceTexts.js
ADDED
|
@@ -0,0 +1,279 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
const express = require('express');
|
| 2 |
+
const jwt = require('jsonwebtoken');
|
| 3 |
+
const SourceText = require('../models/SourceText');
|
| 4 |
+
const router = express.Router();
|
| 5 |
+
|
| 6 |
+
// Middleware to verify JWT token
|
| 7 |
+
const authenticateToken = (req, res, next) => {
|
| 8 |
+
const authHeader = req.headers['authorization'];
|
| 9 |
+
const token = authHeader && authHeader.split(' ')[1];
|
| 10 |
+
|
| 11 |
+
if (!token) {
|
| 12 |
+
return res.status(401).json({ error: 'Access token required' });
|
| 13 |
+
}
|
| 14 |
+
|
| 15 |
+
jwt.verify(token, process.env.JWT_SECRET || 'your-secret-key', (err, user) => {
|
| 16 |
+
if (err) {
|
| 17 |
+
return res.status(403).json({ error: 'Invalid token' });
|
| 18 |
+
}
|
| 19 |
+
req.user = user;
|
| 20 |
+
next();
|
| 21 |
+
});
|
| 22 |
+
};
|
| 23 |
+
|
| 24 |
+
// Get all source texts with filters
|
| 25 |
+
router.get('/', authenticateToken, async (req, res) => {
|
| 26 |
+
try {
|
| 27 |
+
const {
|
| 28 |
+
sourceLanguage,
|
| 29 |
+
sourceCulture,
|
| 30 |
+
targetCulture,
|
| 31 |
+
difficulty,
|
| 32 |
+
sourceType,
|
| 33 |
+
tags,
|
| 34 |
+
page = 1,
|
| 35 |
+
limit = 10
|
| 36 |
+
} = req.query;
|
| 37 |
+
|
| 38 |
+
const filter = { isActive: true };
|
| 39 |
+
|
| 40 |
+
if (sourceLanguage) filter.sourceLanguage = sourceLanguage;
|
| 41 |
+
if (sourceCulture) filter.sourceCulture = sourceCulture;
|
| 42 |
+
if (targetCulture) filter.targetCultures = targetCulture;
|
| 43 |
+
if (difficulty) filter.difficulty = difficulty;
|
| 44 |
+
if (sourceType) filter.sourceType = sourceType;
|
| 45 |
+
if (tags) filter.tags = { $in: tags.split(',') };
|
| 46 |
+
|
| 47 |
+
const skip = (page - 1) * limit;
|
| 48 |
+
|
| 49 |
+
const texts = await SourceText.find(filter)
|
| 50 |
+
.sort({ createdAt: -1 })
|
| 51 |
+
.skip(skip)
|
| 52 |
+
.limit(parseInt(limit))
|
| 53 |
+
.populate('createdBy', 'username');
|
| 54 |
+
|
| 55 |
+
const total = await SourceText.countDocuments(filter);
|
| 56 |
+
|
| 57 |
+
res.json({
|
| 58 |
+
texts,
|
| 59 |
+
pagination: {
|
| 60 |
+
page: parseInt(page),
|
| 61 |
+
limit: parseInt(limit),
|
| 62 |
+
total,
|
| 63 |
+
pages: Math.ceil(total / limit)
|
| 64 |
+
}
|
| 65 |
+
});
|
| 66 |
+
} catch (error) {
|
| 67 |
+
console.error('Get source texts error:', error);
|
| 68 |
+
res.status(500).json({ error: 'Failed to get source texts' });
|
| 69 |
+
}
|
| 70 |
+
});
|
| 71 |
+
|
| 72 |
+
// Get a specific source text by ID
|
| 73 |
+
router.get('/:id', authenticateToken, async (req, res) => {
|
| 74 |
+
try {
|
| 75 |
+
const text = await SourceText.findById(req.params.id)
|
| 76 |
+
.populate('createdBy', 'username');
|
| 77 |
+
|
| 78 |
+
if (!text) {
|
| 79 |
+
return res.status(404).json({ error: 'Source text not found' });
|
| 80 |
+
}
|
| 81 |
+
|
| 82 |
+
// Increment usage count
|
| 83 |
+
text.usageCount += 1;
|
| 84 |
+
await text.save();
|
| 85 |
+
|
| 86 |
+
res.json(text);
|
| 87 |
+
} catch (error) {
|
| 88 |
+
console.error('Get source text error:', error);
|
| 89 |
+
res.status(500).json({ error: 'Failed to get source text' });
|
| 90 |
+
}
|
| 91 |
+
});
|
| 92 |
+
|
| 93 |
+
// Create a new source text
|
| 94 |
+
router.post('/', authenticateToken, async (req, res) => {
|
| 95 |
+
try {
|
| 96 |
+
const {
|
| 97 |
+
title,
|
| 98 |
+
content,
|
| 99 |
+
sourceLanguage,
|
| 100 |
+
sourceCulture,
|
| 101 |
+
sourceUrl,
|
| 102 |
+
sourceType,
|
| 103 |
+
culturalElements,
|
| 104 |
+
difficulty,
|
| 105 |
+
tags,
|
| 106 |
+
context,
|
| 107 |
+
targetCultures
|
| 108 |
+
} = req.body;
|
| 109 |
+
|
| 110 |
+
// Validate required fields
|
| 111 |
+
if (!title || !content || !sourceLanguage || !sourceCulture) {
|
| 112 |
+
return res.status(400).json({
|
| 113 |
+
error: 'Title, content, source language, and source culture are required'
|
| 114 |
+
});
|
| 115 |
+
}
|
| 116 |
+
|
| 117 |
+
const sourceText = new SourceText({
|
| 118 |
+
title,
|
| 119 |
+
content,
|
| 120 |
+
sourceLanguage,
|
| 121 |
+
sourceCulture,
|
| 122 |
+
sourceUrl,
|
| 123 |
+
sourceType,
|
| 124 |
+
culturalElements: culturalElements || [],
|
| 125 |
+
difficulty: difficulty || 'intermediate',
|
| 126 |
+
tags: tags || [],
|
| 127 |
+
context,
|
| 128 |
+
targetCultures: targetCultures || [],
|
| 129 |
+
createdBy: req.user.userId
|
| 130 |
+
});
|
| 131 |
+
|
| 132 |
+
await sourceText.save();
|
| 133 |
+
|
| 134 |
+
res.status(201).json({
|
| 135 |
+
message: 'Source text created successfully',
|
| 136 |
+
sourceText
|
| 137 |
+
});
|
| 138 |
+
} catch (error) {
|
| 139 |
+
console.error('Create source text error:', error);
|
| 140 |
+
res.status(500).json({ error: 'Failed to create source text' });
|
| 141 |
+
}
|
| 142 |
+
});
|
| 143 |
+
|
| 144 |
+
// Update a source text
|
| 145 |
+
router.put('/:id', authenticateToken, async (req, res) => {
|
| 146 |
+
try {
|
| 147 |
+
const sourceText = await SourceText.findById(req.params.id);
|
| 148 |
+
|
| 149 |
+
if (!sourceText) {
|
| 150 |
+
return res.status(404).json({ error: 'Source text not found' });
|
| 151 |
+
}
|
| 152 |
+
|
| 153 |
+
// Only allow creators or admins to update
|
| 154 |
+
if (sourceText.createdBy.toString() !== req.user.userId && req.user.role !== 'admin') {
|
| 155 |
+
return res.status(403).json({ error: 'Not authorized to update this source text' });
|
| 156 |
+
}
|
| 157 |
+
|
| 158 |
+
const updatedText = await SourceText.findByIdAndUpdate(
|
| 159 |
+
req.params.id,
|
| 160 |
+
req.body,
|
| 161 |
+
{ new: true, runValidators: true }
|
| 162 |
+
);
|
| 163 |
+
|
| 164 |
+
res.json({
|
| 165 |
+
message: 'Source text updated successfully',
|
| 166 |
+
sourceText: updatedText
|
| 167 |
+
});
|
| 168 |
+
} catch (error) {
|
| 169 |
+
console.error('Update source text error:', error);
|
| 170 |
+
res.status(500).json({ error: 'Failed to update source text' });
|
| 171 |
+
}
|
| 172 |
+
});
|
| 173 |
+
|
| 174 |
+
// Delete a source text (soft delete)
|
| 175 |
+
router.delete('/:id', authenticateToken, async (req, res) => {
|
| 176 |
+
try {
|
| 177 |
+
const sourceText = await SourceText.findById(req.params.id);
|
| 178 |
+
|
| 179 |
+
if (!sourceText) {
|
| 180 |
+
return res.status(404).json({ error: 'Source text not found' });
|
| 181 |
+
}
|
| 182 |
+
|
| 183 |
+
// Only allow creators or admins to delete
|
| 184 |
+
if (sourceText.createdBy.toString() !== req.user.userId && req.user.role !== 'admin') {
|
| 185 |
+
return res.status(403).json({ error: 'Not authorized to delete this source text' });
|
| 186 |
+
}
|
| 187 |
+
|
| 188 |
+
sourceText.isActive = false;
|
| 189 |
+
await sourceText.save();
|
| 190 |
+
|
| 191 |
+
res.json({ message: 'Source text deleted successfully' });
|
| 192 |
+
} catch (error) {
|
| 193 |
+
console.error('Delete source text error:', error);
|
| 194 |
+
res.status(500).json({ error: 'Failed to delete source text' });
|
| 195 |
+
}
|
| 196 |
+
});
|
| 197 |
+
|
| 198 |
+
// Rate a source text
|
| 199 |
+
router.post('/:id/rate', authenticateToken, async (req, res) => {
|
| 200 |
+
try {
|
| 201 |
+
const { rating } = req.body;
|
| 202 |
+
|
| 203 |
+
if (!rating || rating < 1 || rating > 5) {
|
| 204 |
+
return res.status(400).json({ error: 'Rating must be between 1 and 5' });
|
| 205 |
+
}
|
| 206 |
+
|
| 207 |
+
const sourceText = await SourceText.findById(req.params.id);
|
| 208 |
+
|
| 209 |
+
if (!sourceText) {
|
| 210 |
+
return res.status(404).json({ error: 'Source text not found' });
|
| 211 |
+
}
|
| 212 |
+
|
| 213 |
+
// Update average rating
|
| 214 |
+
const newTotal = (sourceText.averageRating * sourceText.ratingCount) + rating;
|
| 215 |
+
sourceText.ratingCount += 1;
|
| 216 |
+
sourceText.averageRating = newTotal / sourceText.ratingCount;
|
| 217 |
+
|
| 218 |
+
await sourceText.save();
|
| 219 |
+
|
| 220 |
+
res.json({
|
| 221 |
+
message: 'Rating submitted successfully',
|
| 222 |
+
averageRating: sourceText.averageRating,
|
| 223 |
+
ratingCount: sourceText.ratingCount
|
| 224 |
+
});
|
| 225 |
+
} catch (error) {
|
| 226 |
+
console.error('Rate source text error:', error);
|
| 227 |
+
res.status(500).json({ error: 'Failed to submit rating' });
|
| 228 |
+
}
|
| 229 |
+
});
|
| 230 |
+
|
| 231 |
+
// Get cultural elements for a source text
|
| 232 |
+
router.get('/:id/cultural-elements', authenticateToken, async (req, res) => {
|
| 233 |
+
try {
|
| 234 |
+
const sourceText = await SourceText.findById(req.params.id);
|
| 235 |
+
|
| 236 |
+
if (!sourceText) {
|
| 237 |
+
return res.status(404).json({ error: 'Source text not found' });
|
| 238 |
+
}
|
| 239 |
+
|
| 240 |
+
res.json({
|
| 241 |
+
culturalElements: sourceText.culturalElements,
|
| 242 |
+
content: sourceText.content
|
| 243 |
+
});
|
| 244 |
+
} catch (error) {
|
| 245 |
+
console.error('Get cultural elements error:', error);
|
| 246 |
+
res.status(500).json({ error: 'Failed to get cultural elements' });
|
| 247 |
+
}
|
| 248 |
+
});
|
| 249 |
+
|
| 250 |
+
// Highlight cultural elements in text
|
| 251 |
+
router.post('/:id/highlight', authenticateToken, async (req, res) => {
|
| 252 |
+
try {
|
| 253 |
+
const { culturalElements } = req.body;
|
| 254 |
+
|
| 255 |
+
const sourceText = await SourceText.findById(req.params.id);
|
| 256 |
+
|
| 257 |
+
if (!sourceText) {
|
| 258 |
+
return res.status(404).json({ error: 'Source text not found' });
|
| 259 |
+
}
|
| 260 |
+
|
| 261 |
+
// Only allow creators or admins to update cultural elements
|
| 262 |
+
if (sourceText.createdBy.toString() !== req.user.userId && req.user.role !== 'admin') {
|
| 263 |
+
return res.status(403).json({ error: 'Not authorized to update this source text' });
|
| 264 |
+
}
|
| 265 |
+
|
| 266 |
+
sourceText.culturalElements = culturalElements;
|
| 267 |
+
await sourceText.save();
|
| 268 |
+
|
| 269 |
+
res.json({
|
| 270 |
+
message: 'Cultural elements updated successfully',
|
| 271 |
+
culturalElements: sourceText.culturalElements
|
| 272 |
+
});
|
| 273 |
+
} catch (error) {
|
| 274 |
+
console.error('Highlight cultural elements error:', error);
|
| 275 |
+
res.status(500).json({ error: 'Failed to update cultural elements' });
|
| 276 |
+
}
|
| 277 |
+
});
|
| 278 |
+
|
| 279 |
+
module.exports = router;
|
routes/submissions.js
ADDED
|
@@ -0,0 +1,703 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
const express = require('express');
|
| 2 |
+
const Submission = require('../models/Submission');
|
| 3 |
+
const SourceText = require('../models/SourceText');
|
| 4 |
+
const User = require('../models/User');
|
| 5 |
+
const router = express.Router();
|
| 6 |
+
const mongoose = require('mongoose'); // Added for mongoose.Types.ObjectId
|
| 7 |
+
|
| 8 |
+
// Middleware to verify token (simplified)
|
| 9 |
+
const authenticateToken = (req, res, next) => {
|
| 10 |
+
const authHeader = req.headers['authorization'];
|
| 11 |
+
const token = authHeader && authHeader.split(' ')[1];
|
| 12 |
+
|
| 13 |
+
if (!token) {
|
| 14 |
+
return res.status(401).json({ error: 'Access token required' });
|
| 15 |
+
}
|
| 16 |
+
|
| 17 |
+
// For our simplified system, just check if token exists and has the right format
|
| 18 |
+
if (token.startsWith('user_') || token.startsWith('visitor_')) {
|
| 19 |
+
// Extract user ID from token and create a consistent ObjectId
|
| 20 |
+
const userIdString = token.split('_')[1] || 'default_user';
|
| 21 |
+
|
| 22 |
+
// Create a consistent ObjectId based on the user token
|
| 23 |
+
// This ensures the same user always gets the same ObjectId across requests
|
| 24 |
+
const crypto = require('crypto');
|
| 25 |
+
const hash = crypto.createHash('md5').update(userIdString).digest('hex');
|
| 26 |
+
const consistentObjectId = new mongoose.Types.ObjectId(hash.substring(0, 24));
|
| 27 |
+
|
| 28 |
+
// Get user role from headers (frontend will send this)
|
| 29 |
+
const userRole = req.headers['user-role'] || 'visitor';
|
| 30 |
+
|
| 31 |
+
req.user = {
|
| 32 |
+
userId: consistentObjectId,
|
| 33 |
+
token,
|
| 34 |
+
role: userRole
|
| 35 |
+
};
|
| 36 |
+
next();
|
| 37 |
+
} else {
|
| 38 |
+
return res.status(403).json({ error: 'Invalid token' });
|
| 39 |
+
}
|
| 40 |
+
};
|
| 41 |
+
|
| 42 |
+
// Get all submissions for voting (anonymized) - MOVED TO TOP
|
| 43 |
+
router.get('/voteable', authenticateToken, async (req, res) => {
|
| 44 |
+
try {
|
| 45 |
+
// Use the consistent user ID from authentication middleware
|
| 46 |
+
const userId = req.user.userId;
|
| 47 |
+
|
| 48 |
+
// Get all submissions that are ready for voting (limit to 500 to prevent server overload)
|
| 49 |
+
const submissions = await Submission.find({
|
| 50 |
+
status: { $in: ['submitted', 'reviewed', 'approved'] }
|
| 51 |
+
})
|
| 52 |
+
.sort({ createdAt: -1 })
|
| 53 |
+
.limit(500)
|
| 54 |
+
.populate('sourceTextId', 'content sourceLanguage category weekNumber title');
|
| 55 |
+
|
| 56 |
+
// Group submissions by source text (example)
|
| 57 |
+
const groupedByExample = {};
|
| 58 |
+
|
| 59 |
+
submissions.forEach(submission => {
|
| 60 |
+
if (!submission.sourceTextId) {
|
| 61 |
+
return;
|
| 62 |
+
}
|
| 63 |
+
|
| 64 |
+
const sourceTextId = submission.sourceTextId._id.toString();
|
| 65 |
+
if (!groupedByExample[sourceTextId]) {
|
| 66 |
+
groupedByExample[sourceTextId] = {
|
| 67 |
+
example: {
|
| 68 |
+
id: sourceTextId,
|
| 69 |
+
content: submission.sourceTextId.content,
|
| 70 |
+
language: submission.sourceTextId.sourceLanguage,
|
| 71 |
+
culture: 'Western',
|
| 72 |
+
category: submission.sourceTextId.category || 'tutorial',
|
| 73 |
+
weekNumber: submission.sourceTextId.weekNumber || 1,
|
| 74 |
+
title: submission.sourceTextId.title || `Example ${sourceTextId.slice(-4)}`
|
| 75 |
+
},
|
| 76 |
+
translations: []
|
| 77 |
+
};
|
| 78 |
+
}
|
| 79 |
+
|
| 80 |
+
// Anonymize the user and handle group submissions
|
| 81 |
+
const anonymizedUser = {
|
| 82 |
+
_id: submission.userId,
|
| 83 |
+
name: submission.groupNumber ? `Group ${submission.groupNumber}` : `Student_${submission.userId.toString().slice(-4)}`
|
| 84 |
+
};
|
| 85 |
+
|
| 86 |
+
// Calculate vote score and counts
|
| 87 |
+
const score = submission.calculateScore();
|
| 88 |
+
const voteCounts = submission.getVoteCountByRank();
|
| 89 |
+
|
| 90 |
+
// Check if current user has voted on this submission
|
| 91 |
+
const userVote = submission.votes.find(v => v.userId.toString() === userId.toString());
|
| 92 |
+
const hasVoted = !!userVote;
|
| 93 |
+
const userRank = userVote ? userVote.rank : null;
|
| 94 |
+
|
| 95 |
+
groupedByExample[sourceTextId].translations.push({
|
| 96 |
+
id: submission._id,
|
| 97 |
+
translation: submission.transcreation,
|
| 98 |
+
score,
|
| 99 |
+
voteCounts,
|
| 100 |
+
totalVotes: submission.votes.length,
|
| 101 |
+
createdAt: submission.createdAt,
|
| 102 |
+
hasVoted,
|
| 103 |
+
userRank,
|
| 104 |
+
groupNumber: submission.groupNumber,
|
| 105 |
+
isGroupSubmission: !!submission.groupNumber
|
| 106 |
+
});
|
| 107 |
+
});
|
| 108 |
+
|
| 109 |
+
// Convert to array format for frontend and sort by week number, then category, then title
|
| 110 |
+
const voteableExamples = Object.values(groupedByExample)
|
| 111 |
+
.sort((a, b) => {
|
| 112 |
+
// First sort by week number
|
| 113 |
+
if (a.example.weekNumber !== b.example.weekNumber) {
|
| 114 |
+
return a.example.weekNumber - b.example.weekNumber;
|
| 115 |
+
}
|
| 116 |
+
|
| 117 |
+
// Then sort by category (tutorial first, then weekly-practice)
|
| 118 |
+
if (a.example.category !== b.example.category) {
|
| 119 |
+
if (a.example.category === 'tutorial') return -1;
|
| 120 |
+
if (b.example.category === 'tutorial') return 1;
|
| 121 |
+
}
|
| 122 |
+
|
| 123 |
+
// Finally sort by title to maintain consistent order
|
| 124 |
+
return a.example.title.localeCompare(b.example.title);
|
| 125 |
+
})
|
| 126 |
+
.map(group => ({
|
| 127 |
+
example: group.example,
|
| 128 |
+
translations: group.translations.sort((a, b) => b.score - a.score)
|
| 129 |
+
}));
|
| 130 |
+
|
| 131 |
+
res.json({
|
| 132 |
+
examples: voteableExamples
|
| 133 |
+
});
|
| 134 |
+
} catch (error) {
|
| 135 |
+
console.error('Get voteable submissions error:', error);
|
| 136 |
+
res.status(500).json({ error: 'Failed to get voteable submissions' });
|
| 137 |
+
}
|
| 138 |
+
});
|
| 139 |
+
|
| 140 |
+
// Submit a new transcreation
|
| 141 |
+
router.post('/', authenticateToken, async (req, res) => {
|
| 142 |
+
try {
|
| 143 |
+
const {
|
| 144 |
+
sourceTextId,
|
| 145 |
+
targetCulture,
|
| 146 |
+
targetLanguage,
|
| 147 |
+
transcreation,
|
| 148 |
+
explanation,
|
| 149 |
+
culturalAdaptations = [],
|
| 150 |
+
isAnonymous = true,
|
| 151 |
+
groupNumber
|
| 152 |
+
} = req.body;
|
| 153 |
+
|
| 154 |
+
// Validate required fields
|
| 155 |
+
if (!sourceTextId || !transcreation) {
|
| 156 |
+
return res.status(400).json({
|
| 157 |
+
error: 'Source text ID and transcreation are required'
|
| 158 |
+
});
|
| 159 |
+
}
|
| 160 |
+
|
| 161 |
+
// Check if source text exists
|
| 162 |
+
const sourceText = await SourceText.findById(sourceTextId);
|
| 163 |
+
if (!sourceText) {
|
| 164 |
+
return res.status(404).json({ error: 'Source text not found' });
|
| 165 |
+
}
|
| 166 |
+
|
| 167 |
+
// Use the consistent user ID from authentication middleware
|
| 168 |
+
const userId = req.user.userId;
|
| 169 |
+
|
| 170 |
+
// For tutorial tasks, group number is required
|
| 171 |
+
if (sourceText.category === 'tutorial') {
|
| 172 |
+
if (!groupNumber || groupNumber < 1 || groupNumber > 8) {
|
| 173 |
+
return res.status(400).json({
|
| 174 |
+
error: 'Group number (1-8) is required for tutorial task submissions'
|
| 175 |
+
});
|
| 176 |
+
}
|
| 177 |
+
|
| 178 |
+
// Check if group already submitted for this source text
|
| 179 |
+
const existingGroupSubmission = await Submission.findOne({
|
| 180 |
+
sourceTextId,
|
| 181 |
+
groupNumber,
|
| 182 |
+
targetCulture
|
| 183 |
+
});
|
| 184 |
+
|
| 185 |
+
if (existingGroupSubmission) {
|
| 186 |
+
return res.status(400).json({
|
| 187 |
+
error: 'Your group has already submitted a transcreation for this tutorial task'
|
| 188 |
+
});
|
| 189 |
+
}
|
| 190 |
+
} else {
|
| 191 |
+
// For non-tutorial tasks, check if user already submitted
|
| 192 |
+
const existingSubmission = await Submission.findOne({
|
| 193 |
+
sourceTextId,
|
| 194 |
+
userId: userId,
|
| 195 |
+
targetCulture
|
| 196 |
+
});
|
| 197 |
+
|
| 198 |
+
if (existingSubmission) {
|
| 199 |
+
return res.status(400).json({
|
| 200 |
+
error: 'You have already submitted a transcreation for this source text and target culture'
|
| 201 |
+
});
|
| 202 |
+
}
|
| 203 |
+
}
|
| 204 |
+
|
| 205 |
+
// Extract username from token - use a more reliable method
|
| 206 |
+
let username = 'Unknown';
|
| 207 |
+
// For now, we'll use a generic approach since the token doesn't contain the username
|
| 208 |
+
// The frontend should send the username in the request body or we should store it differently
|
| 209 |
+
if (req.body.username) {
|
| 210 |
+
username = req.body.username;
|
| 211 |
+
} else if (req.user.name) {
|
| 212 |
+
username = req.user.name;
|
| 213 |
+
} else {
|
| 214 |
+
// Fallback to a generic identifier
|
| 215 |
+
username = `User_${Date.now().toString().slice(-4)}`;
|
| 216 |
+
}
|
| 217 |
+
|
| 218 |
+
const submission = new Submission({
|
| 219 |
+
sourceTextId,
|
| 220 |
+
userId: userId,
|
| 221 |
+
username: username,
|
| 222 |
+
groupNumber: sourceText.category === 'tutorial' ? groupNumber : undefined,
|
| 223 |
+
targetCulture: targetCulture || 'Western',
|
| 224 |
+
targetLanguage: targetLanguage || 'English',
|
| 225 |
+
transcreation,
|
| 226 |
+
explanation: explanation || 'Translation submission',
|
| 227 |
+
culturalAdaptations,
|
| 228 |
+
isAnonymous: isAnonymous !== undefined ? isAnonymous : true,
|
| 229 |
+
status: 'submitted',
|
| 230 |
+
difficulty: sourceText.difficulty || 'intermediate'
|
| 231 |
+
});
|
| 232 |
+
|
| 233 |
+
|
| 234 |
+
|
| 235 |
+
await submission.save();
|
| 236 |
+
|
| 237 |
+
res.status(201).json({
|
| 238 |
+
message: 'Transcreation submitted successfully',
|
| 239 |
+
submission
|
| 240 |
+
});
|
| 241 |
+
} catch (error) {
|
| 242 |
+
console.error('Submit transcreation error:', error);
|
| 243 |
+
res.status(500).json({ error: 'Failed to submit transcreation' });
|
| 244 |
+
}
|
| 245 |
+
});
|
| 246 |
+
|
| 247 |
+
// Get all submissions for a source text (anonymized)
|
| 248 |
+
router.get('/source-text/:sourceTextId', authenticateToken, async (req, res) => {
|
| 249 |
+
try {
|
| 250 |
+
const { targetCulture, page = 1, limit = 10 } = req.query;
|
| 251 |
+
const skip = (page - 1) * limit;
|
| 252 |
+
|
| 253 |
+
const filter = {
|
| 254 |
+
sourceTextId: req.params.sourceTextId,
|
| 255 |
+
status: { $in: ['submitted', 'reviewed', 'approved'] }
|
| 256 |
+
};
|
| 257 |
+
|
| 258 |
+
if (targetCulture) {
|
| 259 |
+
filter.targetCulture = targetCulture;
|
| 260 |
+
}
|
| 261 |
+
|
| 262 |
+
const submissions = await Submission.find(filter)
|
| 263 |
+
.sort({ score: -1, createdAt: -1 })
|
| 264 |
+
.skip(skip)
|
| 265 |
+
.limit(parseInt(limit))
|
| 266 |
+
.populate('userId', 'username')
|
| 267 |
+
.populate('sourceTextId', 'title content');
|
| 268 |
+
|
| 269 |
+
// Anonymize submissions
|
| 270 |
+
const anonymizedSubmissions = submissions.map(submission => {
|
| 271 |
+
const submissionObj = submission.toObject();
|
| 272 |
+
|
| 273 |
+
if (submission.isAnonymous) {
|
| 274 |
+
submissionObj.userId = {
|
| 275 |
+
_id: submission.userId._id,
|
| 276 |
+
username: `Student_${submission.userId._id.toString().slice(-4)}`
|
| 277 |
+
};
|
| 278 |
+
} else {
|
| 279 |
+
// Always use the stored username for non-anonymous submissions
|
| 280 |
+
submissionObj.userId = {
|
| 281 |
+
_id: submission.userId._id || submission.userId,
|
| 282 |
+
username: submission.username || 'Unknown'
|
| 283 |
+
};
|
| 284 |
+
}
|
| 285 |
+
|
| 286 |
+
return submissionObj;
|
| 287 |
+
});
|
| 288 |
+
|
| 289 |
+
const total = await Submission.countDocuments(filter);
|
| 290 |
+
|
| 291 |
+
res.json({
|
| 292 |
+
submissions: anonymizedSubmissions,
|
| 293 |
+
pagination: {
|
| 294 |
+
page: parseInt(page),
|
| 295 |
+
limit: parseInt(limit),
|
| 296 |
+
total,
|
| 297 |
+
pages: Math.ceil(total / limit)
|
| 298 |
+
}
|
| 299 |
+
});
|
| 300 |
+
} catch (error) {
|
| 301 |
+
console.error('Get submissions error:', error);
|
| 302 |
+
res.status(500).json({ error: 'Failed to get submissions' });
|
| 303 |
+
}
|
| 304 |
+
});
|
| 305 |
+
|
| 306 |
+
// Get all submissions for source texts (visible to all users)
|
| 307 |
+
router.get('/my-submissions', authenticateToken, async (req, res) => {
|
| 308 |
+
try {
|
| 309 |
+
// Get all submissions with source text info (limit to 1000 to prevent server overload)
|
| 310 |
+
const submissions = await Submission.find({})
|
| 311 |
+
.sort({ createdAt: -1 })
|
| 312 |
+
.limit(1000)
|
| 313 |
+
.populate('sourceTextId', 'title content sourceLanguage category weekNumber');
|
| 314 |
+
|
| 315 |
+
// Add user info to each submission
|
| 316 |
+
const submissionsWithUserInfo = submissions.map(submission => {
|
| 317 |
+
const submissionObj = submission.toObject();
|
| 318 |
+
submissionObj.isOwner = submission.userId && submission.userId.toString() === req.user.userId.toString();
|
| 319 |
+
|
| 320 |
+
// Handle anonymous submissions and create user info
|
| 321 |
+
let username = 'Unknown';
|
| 322 |
+
if (submission.isAnonymous && !submissionObj.isOwner) {
|
| 323 |
+
username = 'Anonymous';
|
| 324 |
+
} else {
|
| 325 |
+
// For non-anonymous or owner viewing, show a meaningful identifier
|
| 326 |
+
if (submission.groupNumber) {
|
| 327 |
+
username = `Group ${submission.groupNumber}`;
|
| 328 |
+
} else {
|
| 329 |
+
// Use the stored username for weekly practice submissions
|
| 330 |
+
username = submission.username || 'Unknown';
|
| 331 |
+
}
|
| 332 |
+
}
|
| 333 |
+
|
| 334 |
+
submissionObj.userId = {
|
| 335 |
+
_id: submission.userId || 'Unknown',
|
| 336 |
+
username: username
|
| 337 |
+
};
|
| 338 |
+
|
| 339 |
+
// Include isAnonymous field for frontend
|
| 340 |
+
submissionObj.isAnonymous = submission.isAnonymous;
|
| 341 |
+
|
| 342 |
+
// Add vote counts
|
| 343 |
+
submissionObj.voteCounts = submission.getVoteCountByRank();
|
| 344 |
+
return submissionObj;
|
| 345 |
+
});
|
| 346 |
+
|
| 347 |
+
res.json({
|
| 348 |
+
submissions: submissionsWithUserInfo
|
| 349 |
+
});
|
| 350 |
+
} catch (error) {
|
| 351 |
+
console.error('Get submissions error:', error);
|
| 352 |
+
res.status(500).json({ error: 'Failed to get submissions' });
|
| 353 |
+
}
|
| 354 |
+
});
|
| 355 |
+
|
| 356 |
+
// Update a submission (only by owner)
|
| 357 |
+
router.put('/:submissionId', authenticateToken, async (req, res) => {
|
| 358 |
+
try {
|
| 359 |
+
const { submissionId } = req.params;
|
| 360 |
+
const { transcreation } = req.body;
|
| 361 |
+
|
| 362 |
+
if (!transcreation) {
|
| 363 |
+
return res.status(400).json({ error: 'Transcreation is required' });
|
| 364 |
+
}
|
| 365 |
+
|
| 366 |
+
const submission = await Submission.findById(submissionId);
|
| 367 |
+
if (!submission) {
|
| 368 |
+
return res.status(404).json({ error: 'Submission not found' });
|
| 369 |
+
}
|
| 370 |
+
|
| 371 |
+
// Check if user owns this submission
|
| 372 |
+
if (submission.userId.toString() !== req.user.userId.toString()) {
|
| 373 |
+
return res.status(403).json({ error: 'You can only edit your own submissions' });
|
| 374 |
+
}
|
| 375 |
+
|
| 376 |
+
submission.transcreation = transcreation;
|
| 377 |
+
await submission.save();
|
| 378 |
+
|
| 379 |
+
res.json({ message: 'Submission updated successfully', submission });
|
| 380 |
+
} catch (error) {
|
| 381 |
+
console.error('Update submission error:', error);
|
| 382 |
+
res.status(500).json({ error: 'Failed to update submission' });
|
| 383 |
+
}
|
| 384 |
+
});
|
| 385 |
+
|
| 386 |
+
// Delete a submission (only by admin or owner)
|
| 387 |
+
router.delete('/:submissionId', authenticateToken, async (req, res) => {
|
| 388 |
+
try {
|
| 389 |
+
const { submissionId } = req.params;
|
| 390 |
+
const user = req.user;
|
| 391 |
+
|
| 392 |
+
const submission = await Submission.findById(submissionId);
|
| 393 |
+
if (!submission) {
|
| 394 |
+
return res.status(404).json({ error: 'Submission not found' });
|
| 395 |
+
}
|
| 396 |
+
|
| 397 |
+
// Check if user is admin or owns this submission
|
| 398 |
+
const isAdmin = user.role === 'admin';
|
| 399 |
+
const isOwner = submission.userId.toString() === user.userId.toString();
|
| 400 |
+
|
| 401 |
+
if (!isAdmin && !isOwner) {
|
| 402 |
+
return res.status(403).json({ error: 'You can only delete your own submissions or must be an admin' });
|
| 403 |
+
}
|
| 404 |
+
|
| 405 |
+
await Submission.findByIdAndDelete(submissionId);
|
| 406 |
+
|
| 407 |
+
res.json({ message: 'Submission deleted successfully' });
|
| 408 |
+
} catch (error) {
|
| 409 |
+
console.error('Delete submission error:', error);
|
| 410 |
+
res.status(500).json({ error: 'Failed to delete submission' });
|
| 411 |
+
}
|
| 412 |
+
});
|
| 413 |
+
|
| 414 |
+
// Get a specific submission
|
| 415 |
+
router.get('/:id', authenticateToken, async (req, res) => {
|
| 416 |
+
try {
|
| 417 |
+
const submission = await Submission.findById(req.params.id)
|
| 418 |
+
.populate('userId', 'username')
|
| 419 |
+
.populate('sourceTextId', 'title content sourceLanguage culturalElements');
|
| 420 |
+
|
| 421 |
+
if (!submission) {
|
| 422 |
+
return res.status(404).json({ error: 'Submission not found' });
|
| 423 |
+
}
|
| 424 |
+
|
| 425 |
+
// Check if user can view this submission
|
| 426 |
+
if (submission.userId._id.toString() !== req.user.userId && req.user.role === 'student') {
|
| 427 |
+
return res.status(403).json({ error: 'Not authorized to view this submission' });
|
| 428 |
+
}
|
| 429 |
+
|
| 430 |
+
// Anonymize if needed
|
| 431 |
+
if (submission.isAnonymous && submission.userId._id.toString() !== req.user.userId) {
|
| 432 |
+
submission.userId = {
|
| 433 |
+
_id: submission.userId._id,
|
| 434 |
+
username: `Student_${submission.userId._id.toString().slice(-4)}`
|
| 435 |
+
};
|
| 436 |
+
}
|
| 437 |
+
|
| 438 |
+
res.json(submission);
|
| 439 |
+
} catch (error) {
|
| 440 |
+
console.error('Get submission error:', error);
|
| 441 |
+
res.status(500).json({ error: 'Failed to get submission' });
|
| 442 |
+
}
|
| 443 |
+
});
|
| 444 |
+
|
| 445 |
+
// Vote on a submission (top 3 voting with constraints)
|
| 446 |
+
router.post('/:id/vote', authenticateToken, async (req, res) => {
|
| 447 |
+
try {
|
| 448 |
+
const { rank, cancel } = req.body; // rank: 1, 2, or 3 for 1st, 2nd, or 3rd place, cancel: true to cancel vote
|
| 449 |
+
|
| 450 |
+
// Use the consistent user ID from authentication middleware
|
| 451 |
+
const userId = req.user.userId;
|
| 452 |
+
|
| 453 |
+
// Handle vote cancellation
|
| 454 |
+
if (cancel) {
|
| 455 |
+
const submission = await Submission.findById(req.params.id);
|
| 456 |
+
|
| 457 |
+
if (!submission) {
|
| 458 |
+
return res.status(404).json({ error: 'Submission not found' });
|
| 459 |
+
}
|
| 460 |
+
|
| 461 |
+
// Remove the user's vote from this submission
|
| 462 |
+
submission.votes = submission.votes.filter(v => v.userId.toString() !== userId.toString());
|
| 463 |
+
|
| 464 |
+
await submission.save();
|
| 465 |
+
|
| 466 |
+
// Calculate new score and vote counts
|
| 467 |
+
const score = submission.calculateScore();
|
| 468 |
+
const voteCounts = submission.getVoteCountByRank();
|
| 469 |
+
|
| 470 |
+
res.json({
|
| 471 |
+
message: 'Vote cancelled successfully',
|
| 472 |
+
score,
|
| 473 |
+
voteCounts,
|
| 474 |
+
totalVotes: submission.votes.length,
|
| 475 |
+
userRank: null
|
| 476 |
+
});
|
| 477 |
+
return;
|
| 478 |
+
}
|
| 479 |
+
|
| 480 |
+
// Handle vote submission
|
| 481 |
+
if (!rank || ![1, 2, 3].includes(rank)) {
|
| 482 |
+
return res.status(400).json({ error: 'Rank must be 1 (1st place), 2 (2nd place), or 3 (3rd place)' });
|
| 483 |
+
}
|
| 484 |
+
|
| 485 |
+
const submission = await Submission.findById(req.params.id);
|
| 486 |
+
|
| 487 |
+
if (!submission) {
|
| 488 |
+
return res.status(404).json({ error: 'Submission not found' });
|
| 489 |
+
}
|
| 490 |
+
|
| 491 |
+
// Check if user already voted on this submission
|
| 492 |
+
const existingVote = submission.votes.find(v => v.userId.toString() === userId.toString());
|
| 493 |
+
|
| 494 |
+
if (existingVote) {
|
| 495 |
+
// Update existing vote
|
| 496 |
+
existingVote.rank = rank;
|
| 497 |
+
existingVote.createdAt = Date.now();
|
| 498 |
+
} else {
|
| 499 |
+
// For now, allow voting without strict constraints
|
| 500 |
+
// In a real implementation, you'd want to enforce the 3-vote limit per example
|
| 501 |
+
submission.votes.push({
|
| 502 |
+
userId: userId,
|
| 503 |
+
rank,
|
| 504 |
+
createdAt: Date.now()
|
| 505 |
+
});
|
| 506 |
+
}
|
| 507 |
+
|
| 508 |
+
await submission.save();
|
| 509 |
+
|
| 510 |
+
// Calculate new score and vote counts
|
| 511 |
+
const score = submission.calculateScore();
|
| 512 |
+
const voteCounts = submission.getVoteCountByRank();
|
| 513 |
+
|
| 514 |
+
res.json({
|
| 515 |
+
message: 'Vote submitted successfully',
|
| 516 |
+
score,
|
| 517 |
+
voteCounts,
|
| 518 |
+
totalVotes: submission.votes.length,
|
| 519 |
+
userRank: rank
|
| 520 |
+
});
|
| 521 |
+
} catch (error) {
|
| 522 |
+
console.error('Vote submission error:', error);
|
| 523 |
+
res.status(500).json({ error: 'Failed to submit vote' });
|
| 524 |
+
}
|
| 525 |
+
});
|
| 526 |
+
|
| 527 |
+
// Add feedback to a submission
|
| 528 |
+
router.post('/:id/feedback', authenticateToken, async (req, res) => {
|
| 529 |
+
try {
|
| 530 |
+
const { comment, rating } = req.body;
|
| 531 |
+
|
| 532 |
+
if (!comment) {
|
| 533 |
+
return res.status(400).json({ error: 'Comment is required' });
|
| 534 |
+
}
|
| 535 |
+
|
| 536 |
+
if (rating && (rating < 1 || rating > 5)) {
|
| 537 |
+
return res.status(400).json({ error: 'Rating must be between 1 and 5' });
|
| 538 |
+
}
|
| 539 |
+
|
| 540 |
+
const submission = await Submission.findById(req.params.id);
|
| 541 |
+
|
| 542 |
+
if (!submission) {
|
| 543 |
+
return res.status(404).json({ error: 'Submission not found' });
|
| 544 |
+
}
|
| 545 |
+
|
| 546 |
+
// Check if user already provided feedback
|
| 547 |
+
const existingFeedback = submission.feedback.find(f => f.userId.toString() === req.user.userId);
|
| 548 |
+
|
| 549 |
+
if (existingFeedback) {
|
| 550 |
+
return res.status(400).json({ error: 'You have already provided feedback for this submission' });
|
| 551 |
+
}
|
| 552 |
+
|
| 553 |
+
submission.feedback.push({
|
| 554 |
+
userId: req.user.userId,
|
| 555 |
+
comment,
|
| 556 |
+
rating,
|
| 557 |
+
createdAt: Date.now()
|
| 558 |
+
});
|
| 559 |
+
|
| 560 |
+
await submission.save();
|
| 561 |
+
|
| 562 |
+
res.json({
|
| 563 |
+
message: 'Feedback submitted successfully',
|
| 564 |
+
feedbackCount: submission.feedback.length
|
| 565 |
+
});
|
| 566 |
+
} catch (error) {
|
| 567 |
+
console.error('Add feedback error:', error);
|
| 568 |
+
res.status(500).json({ error: 'Failed to add feedback' });
|
| 569 |
+
}
|
| 570 |
+
});
|
| 571 |
+
|
| 572 |
+
// Get feedback for a submission
|
| 573 |
+
router.get('/:id/feedback', authenticateToken, async (req, res) => {
|
| 574 |
+
try {
|
| 575 |
+
const submission = await Submission.findById(req.params.id)
|
| 576 |
+
.populate('feedback.userId', 'username');
|
| 577 |
+
|
| 578 |
+
if (!submission) {
|
| 579 |
+
return res.status(404).json({ error: 'Submission not found' });
|
| 580 |
+
}
|
| 581 |
+
|
| 582 |
+
// Anonymize feedback if submission is anonymous
|
| 583 |
+
const anonymizedFeedback = submission.feedback.map(feedback => {
|
| 584 |
+
const feedbackObj = feedback.toObject();
|
| 585 |
+
|
| 586 |
+
if (submission.isAnonymous && feedback.userId._id.toString() !== req.user.userId) {
|
| 587 |
+
feedbackObj.userId = {
|
| 588 |
+
_id: feedback.userId._id,
|
| 589 |
+
username: `Student_${feedback.userId._id.toString().slice(-4)}`
|
| 590 |
+
};
|
| 591 |
+
}
|
| 592 |
+
|
| 593 |
+
return feedbackObj;
|
| 594 |
+
});
|
| 595 |
+
|
| 596 |
+
res.json({
|
| 597 |
+
feedback: anonymizedFeedback,
|
| 598 |
+
totalFeedback: submission.feedback.length
|
| 599 |
+
});
|
| 600 |
+
} catch (error) {
|
| 601 |
+
console.error('Get feedback error:', error);
|
| 602 |
+
res.status(500).json({ error: 'Failed to get feedback' });
|
| 603 |
+
}
|
| 604 |
+
});
|
| 605 |
+
|
| 606 |
+
// Update submission
|
| 607 |
+
router.put('/:id', authenticateToken, async (req, res) => {
|
| 608 |
+
try {
|
| 609 |
+
const submission = await Submission.findById(req.params.id);
|
| 610 |
+
|
| 611 |
+
if (!submission) {
|
| 612 |
+
return res.status(404).json({ error: 'Submission not found' });
|
| 613 |
+
}
|
| 614 |
+
|
| 615 |
+
// Only allow submission owner to update
|
| 616 |
+
if (submission.userId.toString() !== req.user.userId) {
|
| 617 |
+
return res.status(403).json({ error: 'Not authorized to update this submission' });
|
| 618 |
+
}
|
| 619 |
+
|
| 620 |
+
// Only allow updates if submission is still in draft or submitted status
|
| 621 |
+
if (!['draft', 'submitted'].includes(submission.status)) {
|
| 622 |
+
return res.status(400).json({ error: 'Cannot update submission in current status' });
|
| 623 |
+
}
|
| 624 |
+
|
| 625 |
+
const updatedSubmission = await Submission.findByIdAndUpdate(
|
| 626 |
+
req.params.id,
|
| 627 |
+
req.body,
|
| 628 |
+
{ new: true, runValidators: true }
|
| 629 |
+
);
|
| 630 |
+
|
| 631 |
+
res.json({
|
| 632 |
+
message: 'Submission updated successfully',
|
| 633 |
+
submission: updatedSubmission
|
| 634 |
+
});
|
| 635 |
+
} catch (error) {
|
| 636 |
+
console.error('Update submission error:', error);
|
| 637 |
+
res.status(500).json({ error: 'Failed to update submission' });
|
| 638 |
+
}
|
| 639 |
+
});
|
| 640 |
+
|
| 641 |
+
// Delete submission
|
| 642 |
+
router.delete('/:id', authenticateToken, async (req, res) => {
|
| 643 |
+
try {
|
| 644 |
+
const submission = await Submission.findById(req.params.id);
|
| 645 |
+
|
| 646 |
+
if (!submission) {
|
| 647 |
+
return res.status(404).json({ error: 'Submission not found' });
|
| 648 |
+
}
|
| 649 |
+
|
| 650 |
+
// Only allow submission owner to delete
|
| 651 |
+
if (submission.userId.toString() !== req.user.userId) {
|
| 652 |
+
return res.status(403).json({ error: 'Not authorized to delete this submission' });
|
| 653 |
+
}
|
| 654 |
+
|
| 655 |
+
await Submission.findByIdAndDelete(req.params.id);
|
| 656 |
+
|
| 657 |
+
res.json({ message: 'Submission deleted successfully' });
|
| 658 |
+
} catch (error) {
|
| 659 |
+
console.error('Delete submission error:', error);
|
| 660 |
+
res.status(500).json({ error: 'Failed to delete submission' });
|
| 661 |
+
}
|
| 662 |
+
});
|
| 663 |
+
|
| 664 |
+
// Get submission statistics
|
| 665 |
+
router.get('/stats/source-text/:sourceTextId', authenticateToken, async (req, res) => {
|
| 666 |
+
try {
|
| 667 |
+
const { targetCulture } = req.query;
|
| 668 |
+
|
| 669 |
+
const filter = { sourceTextId: req.params.sourceTextId };
|
| 670 |
+
if (targetCulture) {
|
| 671 |
+
filter.targetCulture = targetCulture;
|
| 672 |
+
}
|
| 673 |
+
|
| 674 |
+
const stats = await Submission.aggregate([
|
| 675 |
+
{ $match: filter },
|
| 676 |
+
{
|
| 677 |
+
$group: {
|
| 678 |
+
_id: null,
|
| 679 |
+
totalSubmissions: { $sum: 1 },
|
| 680 |
+
averageScore: { $avg: '$score' },
|
| 681 |
+
totalVotes: { $sum: { $size: '$votes' } },
|
| 682 |
+
totalFeedback: { $sum: { $size: '$feedback' } },
|
| 683 |
+
targetCultures: { $addToSet: '$targetCulture' }
|
| 684 |
+
}
|
| 685 |
+
}
|
| 686 |
+
]);
|
| 687 |
+
|
| 688 |
+
res.json({
|
| 689 |
+
stats: stats[0] || {
|
| 690 |
+
totalSubmissions: 0,
|
| 691 |
+
averageScore: 0,
|
| 692 |
+
totalVotes: 0,
|
| 693 |
+
totalFeedback: 0,
|
| 694 |
+
targetCultures: []
|
| 695 |
+
}
|
| 696 |
+
});
|
| 697 |
+
} catch (error) {
|
| 698 |
+
console.error('Get submission stats error:', error);
|
| 699 |
+
res.status(500).json({ error: 'Failed to get submission statistics' });
|
| 700 |
+
}
|
| 701 |
+
});
|
| 702 |
+
|
| 703 |
+
module.exports = router;
|
stress-test.js
ADDED
|
@@ -0,0 +1,125 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
const mongoose = require('mongoose');
|
| 2 |
+
const Submission = require('./models/Submission');
|
| 3 |
+
const SourceText = require('./models/SourceText');
|
| 4 |
+
|
| 5 |
+
// Connect to MongoDB
|
| 6 |
+
mongoose.connect('mongodb://localhost:27017/transcreation-sandbox', {
|
| 7 |
+
useNewUrlParser: true,
|
| 8 |
+
useUnifiedTopology: true,
|
| 9 |
+
});
|
| 10 |
+
|
| 11 |
+
const sampleTranscreations = [
|
| 12 |
+
"This is a sample transcreation for stress testing",
|
| 13 |
+
"Another creative translation approach",
|
| 14 |
+
"Cultural adaptation with local flavor",
|
| 15 |
+
"Modern interpretation of the source text",
|
| 16 |
+
"Traditional approach with contemporary twist",
|
| 17 |
+
"Innovative translation strategy",
|
| 18 |
+
"Balanced cultural and linguistic adaptation",
|
| 19 |
+
"Dynamic transcreation with cultural sensitivity",
|
| 20 |
+
"Comprehensive translation solution",
|
| 21 |
+
"Strategic cultural adaptation",
|
| 22 |
+
"Linguistic excellence with cultural awareness",
|
| 23 |
+
"Creative translation methodology",
|
| 24 |
+
"Cultural bridge through translation",
|
| 25 |
+
"Adaptive translation approach",
|
| 26 |
+
"Comprehensive cultural translation"
|
| 27 |
+
];
|
| 28 |
+
|
| 29 |
+
const sampleExplanations = [
|
| 30 |
+
"This translation adapts the cultural context while maintaining the original meaning.",
|
| 31 |
+
"The transcreation considers local cultural nuances and preferences.",
|
| 32 |
+
"Cultural elements have been adapted to resonate with the target audience.",
|
| 33 |
+
"This version maintains the essence while adapting to cultural differences.",
|
| 34 |
+
"The translation strategy focuses on cultural relevance and accessibility.",
|
| 35 |
+
"Adaptive approach that bridges cultural gaps effectively.",
|
| 36 |
+
"Cultural sensitivity guides this translation approach.",
|
| 37 |
+
"This transcreation balances authenticity with cultural adaptation.",
|
| 38 |
+
"Strategic cultural adaptation for maximum impact.",
|
| 39 |
+
"Comprehensive approach to cultural translation challenges.",
|
| 40 |
+
"Linguistic excellence combined with cultural awareness.",
|
| 41 |
+
"Creative methodology for cultural translation.",
|
| 42 |
+
"Cultural bridge building through thoughtful translation.",
|
| 43 |
+
"Adaptive approach to cultural translation challenges.",
|
| 44 |
+
"Comprehensive cultural translation strategy."
|
| 45 |
+
];
|
| 46 |
+
|
| 47 |
+
const sampleUsers = [
|
| 48 |
+
{ name: "Alice Johnson", role: "student" },
|
| 49 |
+
{ name: "Bob Smith", role: "student" },
|
| 50 |
+
{ name: "Carol Davis", role: "student" },
|
| 51 |
+
{ name: "David Wilson", role: "student" },
|
| 52 |
+
{ name: "Eva Brown", role: "student" },
|
| 53 |
+
{ name: "Frank Miller", role: "student" },
|
| 54 |
+
{ name: "Grace Taylor", role: "student" },
|
| 55 |
+
{ name: "Henry Anderson", role: "student" },
|
| 56 |
+
{ name: "Ivy Martinez", role: "student" },
|
| 57 |
+
{ name: "Jack Thompson", role: "student" },
|
| 58 |
+
{ name: "Kate Garcia", role: "student" },
|
| 59 |
+
{ name: "Leo Rodriguez", role: "student" },
|
| 60 |
+
{ name: "Maya Lee", role: "student" },
|
| 61 |
+
{ name: "Noah White", role: "student" },
|
| 62 |
+
{ name: "Olivia Clark", role: "student" }
|
| 63 |
+
];
|
| 64 |
+
|
| 65 |
+
async function createStressTestData() {
|
| 66 |
+
try {
|
| 67 |
+
console.log('Starting stress test data creation...');
|
| 68 |
+
|
| 69 |
+
// Get all source texts
|
| 70 |
+
const sourceTexts = await SourceText.find({});
|
| 71 |
+
console.log(`Found ${sourceTexts.length} source texts`);
|
| 72 |
+
|
| 73 |
+
let totalSubmissions = 0;
|
| 74 |
+
|
| 75 |
+
for (const sourceText of sourceTexts) {
|
| 76 |
+
console.log(`Adding 15 submissions for source text: ${sourceText.title}`);
|
| 77 |
+
|
| 78 |
+
for (let i = 0; i < 15; i++) {
|
| 79 |
+
const user = sampleUsers[i];
|
| 80 |
+
const transcreation = sampleTranscreations[i];
|
| 81 |
+
const explanation = sampleExplanations[i];
|
| 82 |
+
|
| 83 |
+
// Create a consistent ObjectId for the user
|
| 84 |
+
const crypto = require('crypto');
|
| 85 |
+
const hash = crypto.createHash('md5').update(user.name).digest('hex');
|
| 86 |
+
const userId = new mongoose.Types.ObjectId(hash.substring(0, 24));
|
| 87 |
+
|
| 88 |
+
const submissionData = {
|
| 89 |
+
sourceTextId: sourceText._id,
|
| 90 |
+
userId: userId,
|
| 91 |
+
username: user.name,
|
| 92 |
+
groupNumber: sourceText.category === 'tutorial' ? (i % 8) + 1 : undefined,
|
| 93 |
+
targetCulture: 'Western',
|
| 94 |
+
targetLanguage: 'English',
|
| 95 |
+
transcreation: transcreation,
|
| 96 |
+
explanation: explanation,
|
| 97 |
+
culturalAdaptations: ['Cultural adaptation 1', 'Cultural adaptation 2'],
|
| 98 |
+
isAnonymous: Math.random() > 0.5, // Randomly make some anonymous
|
| 99 |
+
status: 'submitted',
|
| 100 |
+
difficulty: ['beginner', 'intermediate', 'advanced'][Math.floor(Math.random() * 3)],
|
| 101 |
+
votes: [],
|
| 102 |
+
feedback: [],
|
| 103 |
+
score: Math.floor(Math.random() * 100),
|
| 104 |
+
createdAt: new Date(Date.now() - Math.random() * 30 * 24 * 60 * 60 * 1000) // Random date within last 30 days
|
| 105 |
+
};
|
| 106 |
+
|
| 107 |
+
const submission = new Submission(submissionData);
|
| 108 |
+
await submission.save();
|
| 109 |
+
totalSubmissions++;
|
| 110 |
+
}
|
| 111 |
+
}
|
| 112 |
+
|
| 113 |
+
console.log(`✅ Successfully created ${totalSubmissions} stress test submissions`);
|
| 114 |
+
console.log(`📊 Total submissions in database: ${await Submission.countDocuments()}`);
|
| 115 |
+
|
| 116 |
+
} catch (error) {
|
| 117 |
+
console.error('❌ Error creating stress test data:', error);
|
| 118 |
+
} finally {
|
| 119 |
+
mongoose.connection.close();
|
| 120 |
+
console.log('Database connection closed');
|
| 121 |
+
}
|
| 122 |
+
}
|
| 123 |
+
|
| 124 |
+
// Run the stress test
|
| 125 |
+
createStressTestData();
|