const express = require('express'); const router = express.Router(); const multer = require('multer'); const WeeklyPracticeFile = require('../models/WeeklyPracticeFile'); const { authenticateToken } = require('./auth'); // Use memory storage and persist to MongoDB Grid-like buffer const upload = multer({ storage: multer.memoryStorage(), limits: { fileSize: 25 * 1024 * 1024 } // 25MB }); // Helper to read role from headers set by frontend interceptor const getRole = (req) => req.headers['user-role'] || 'visitor'; const getUserInfo = (req) => { try { return req.headers['user-info'] ? JSON.parse(req.headers['user-info']) : {}; } catch { return {}; } }; // Consistently decode filenames from latin1 -> utf8 (multer provides latin1 strings) const decodeMaybeLatin1 = (name) => { try { if (!name) return name; return Buffer.from(name, 'latin1').toString('utf8'); } catch (_) { return name; } }; const encodeRFC5987 = (str) => { return encodeURIComponent(str) .replace(/['()]/g, escape) .replace(/\*/g, '%2A'); }; // List files for a given week router.get('/week/:weekNumber', authenticateToken, async (req, res) => { try { const weekNumber = parseInt(req.params.weekNumber); const type = req.query.type; // optional: 'source' | 'translation' const criteria = { weekNumber }; if (type === 'source' || type === 'translation') criteria.type = type; const files = await WeeklyPracticeFile.find(criteria).select('-data').sort({ createdAt: 1 }); res.json({ success: true, files }); } catch (error) { console.error('List weekly practice files error:', error); res.status(500).json({ error: 'Failed to list files' }); } }); // Download file by id router.get('/:id/download', authenticateToken, async (req, res) => { try { const file = await WeeklyPracticeFile.findById(req.params.id); if (!file) return res.status(404).json({ error: 'File not found' }); res.setHeader('Content-Type', file.mimeType); const safeName = decodeMaybeLatin1(file.fileName || 'download'); const asciiFallback = safeName.replace(/[^\x20-\x7E]/g, '_'); const utf8Star = encodeRFC5987(safeName); res.setHeader('Content-Disposition', `attachment; filename="${asciiFallback}"; filename*=UTF-8''${utf8Star}`); return res.send(file.data); } catch (error) { console.error('Download weekly practice file error:', error); res.status(500).json({ error: 'Failed to download file' }); } }); // Admin uploads source files for a week router.post('/week/:weekNumber/source', authenticateToken, upload.single('file'), async (req, res) => { try { if (getRole(req) !== 'admin') return res.status(403).json({ error: 'Admin access required' }); const weekNumber = parseInt(req.params.weekNumber); if (!req.file) return res.status(400).json({ error: 'Missing file' }); const { description } = req.body || {}; const saved = await WeeklyPracticeFile.create({ type: 'source', weekNumber, fileName: decodeMaybeLatin1(req.file.originalname), mimeType: req.file.mimetype, size: req.file.size, data: req.file.buffer, description }); res.status(201).json({ success: true, file: { ...saved.toObject(), data: undefined } }); } catch (error) { console.error('Upload weekly source file error:', error); res.status(500).json({ error: 'Failed to upload source file' }); } }); // Students upload translations, optionally referencing a specific sourceFileId router.post('/week/:weekNumber/translation', authenticateToken, upload.single('file'), async (req, res) => { try { const weekNumber = parseInt(req.params.weekNumber); if (!req.file) return res.status(400).json({ error: 'Missing file' }); const { description, sourceFileId } = req.body || {}; const user = getUserInfo(req); const displayUsername = user?.username || user?.name || user?.displayName || (user?.email ? String(user.email).split('@')[0] : undefined) || 'Unknown User'; const saved = await WeeklyPracticeFile.create({ type: 'translation', weekNumber, sourceFileId: sourceFileId || undefined, userId: user._id || undefined, username: displayUsername, fileName: decodeMaybeLatin1(req.file.originalname), mimeType: req.file.mimetype, size: req.file.size, data: req.file.buffer, description }); res.status(201).json({ success: true, file: { ...saved.toObject(), data: undefined } }); } catch (error) { console.error('Upload weekly translation file error:', error); res.status(500).json({ error: 'Failed to upload translation file' }); } }); // Admin delete a file by id router.delete('/:id', authenticateToken, async (req, res) => { try { if (getRole(req) !== 'admin') return res.status(403).json({ error: 'Admin access required' }); const deleted = await WeeklyPracticeFile.findByIdAndDelete(req.params.id); if (!deleted) return res.status(404).json({ error: 'File not found' }); res.json({ success: true }); } catch (error) { console.error('Delete weekly practice file error:', error); res.status(500).json({ error: 'Failed to delete file' }); } }); module.exports = router;