import express, { Request, Response } from 'express'; import multer from 'multer'; import { DIContainer } from '../../Application/Services/DIContainer'; import { authRequired } from '../../Application/Services/AuthMiddleware'; import { logRequest, logError, logWarning } from '../../Application/Services/Logger'; // Extend Express Request interface for file uploads declare global { namespace Express { interface Request { file?: Express.Multer.File; } } } const router = express.Router(); const container = DIContainer.getInstance(); // Configure multer for file uploads const upload = multer({ storage: multer.memoryStorage(), limits: { fileSize: 10 * 1024 * 1024, // 10MB limit }, fileFilter: (req: any, file: any, cb: any) => { if (file.mimetype === 'application/json' || file.originalname.endsWith('.spr')) { cb(null, true); } else { cb(new Error('Only JSON and .spr files are allowed')); } } }); // Export deck to .spr file (encrypted) - users can only export their own decks router.get('/export/:deckId', authRequired, async (req: Request, res: Response) => { try { const { deckId } = req.params; const userId = (req as any).user.userId; logRequest('Export deck endpoint accessed', req, res, { deckId, userId }); // Check if user owns the deck const deck = await container.deckRepository.findById(deckId); if (!deck) { logWarning('Deck not found for export', { deckId, userId }, req, res); return res.status(404).json({ error: 'Deck not found' }); } // Users can only export their own decks if (deck.userid !== userId) { logWarning('Access denied - user attempted to export deck they do not own', { deckId, userId, deckOwnerId: deck.userid }, req, res); return res.status(403).json({ error: 'Access denied - you can only export your own decks' }); } const sprData = await container.deckImportExportService.exportDeckToSpr(deckId, userId); res.setHeader('Content-Type', 'application/octet-stream'); res.setHeader('Content-Disposition', `attachment; filename="${deck.name || 'deck'}.spr"`); logRequest('Deck exported successfully', req, res, { deckId, userId, deckName: deck.name, fileSize: sprData.length }); res.send(sprData); } catch (error) { logError('Export deck endpoint error', error as Error, req, res); res.status(500).json({ error: 'Internal server error' }); } }); // Import deck from .spr file (encrypted) - imported deck will be owned by the importing user router.post('/import', authRequired, upload.single('file'), async (req: Request, res: Response) => { try { const userId = (req as any).user.userId; logRequest('Import deck endpoint accessed', req, res, { userId, hasFile: !!req.file, fileName: req.file?.originalname, fileSize: req.file?.size }); if (!req.file) { logWarning('No file uploaded for deck import', { userId }, req, res); return res.status(400).json({ error: 'No file uploaded' }); } const fileBuffer = req.file!.buffer; // Import the deck and assign ownership to the current user const result = await container.deckImportExportService.importDeckFromSpr(fileBuffer, userId); logRequest('Deck imported successfully', req, res, { userId, deckId: result.id, deckName: result.name || 'Unknown', fileName: req.file.originalname, fileSize: req.file.size }); res.json({ success: true, message: 'Deck imported successfully and added to your collection', deckId: result.id }); } catch (error) { logError('Import deck endpoint error', error as Error, req, res); if (error instanceof Error && error.message.includes('Invalid')) { return res.status(400).json({ error: 'Invalid file format or corrupted data' }); } else { res.status(500).json({ error: 'Internal server error' }); } } }); export default router;