"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); const express_1 = __importDefault(require("express")); const multer_1 = __importDefault(require("multer")); const DIContainer_1 = require("../../Application/Services/DIContainer"); const AuthMiddleware_1 = require("../../Application/Services/AuthMiddleware"); const ValidationMiddleware_1 = require("../../Application/Services/ValidationMiddleware"); const AdminBypassService_1 = require("../../Application/Services/AdminBypassService"); const Logger_1 = require("../../Application/Services/Logger"); const router = express_1.default.Router(); const container = DIContainer_1.DIContainer.getInstance(); // Configure multer for file uploads const upload = (0, multer_1.default)({ storage: multer_1.default.memoryStorage(), limits: { fileSize: 10 * 1024 * 1024, // 10MB limit }, fileFilter: (req, file, cb) => { if (file.mimetype === 'application/json' || file.originalname.endsWith('.spr')) { cb(null, true); } else { cb(new Error('Only JSON and .spr files are allowed')); } } }); // Helper function to extract language from Accept-Language header function extractLanguageFromAcceptHeader(acceptLanguage) { if (!acceptLanguage) return null; const languages = acceptLanguage.split(','); if (languages.length > 0) { const primaryLanguage = languages[0].split(';')[0].trim().substring(0, 2); return primaryLanguage; } return null; } // ============================================================================= // USER MANAGEMENT ROUTES // ============================================================================= // Get users with pagination (RECOMMENDED) router.get('/users/page/:from/:to', AuthMiddleware_1.adminRequired, async (req, res) => { try { const from = parseInt(req.params.from); const to = parseInt(req.params.to); const includeDeleted = req.query.includeDeleted === 'true'; if (isNaN(from) || isNaN(to) || from < 0 || to < from) { return res.status(400).json({ error: 'Invalid pagination parameters. From and to must be valid numbers with from <= to.' }); } const limit = to - from + 1; if (limit > 100) { return res.status(400).json({ error: 'Page size too large. Maximum 100 records per request.' }); } (0, Logger_1.logRequest)('Admin paginated users endpoint accessed', req, res, { from, to, includeDeleted }); const result = await container.getUsersByPageQueryHandler.execute({ from, to, includeDeleted }); const response = { users: result.users, pagination: { from, to, returned: result.users.length, totalCount: result.totalCount, includeDeleted } }; (0, Logger_1.logRequest)('Admin users retrieved successfully', req, res, { returnedUsers: result.users.length, totalCount: result.totalCount, from, to, includeDeleted }); return res.status(200).json(response); } catch (error) { (0, Logger_1.logError)('Error in admin get users endpoint', error, req, res); return res.status(500).json({ error: 'Internal server error' }); } }); // Get users by page (admin only) - RECOMMENDED router.get('/users/page/:from/:to', AuthMiddleware_1.adminRequired, async (req, res) => { try { const from = parseInt(req.params.from); const to = parseInt(req.params.to); const includeDeleted = req.query.includeDeleted === 'true'; if (isNaN(from) || isNaN(to) || from < 0 || to < from) { return res.status(400).json({ error: 'Invalid page parameters. "from" and "to" must be valid numbers with to >= from >= 0' }); } (0, Logger_1.logRequest)('Admin get users by page endpoint accessed', req, res, { from, to, includeDeleted }); const result = includeDeleted ? await container.userRepository.findByPageIncludingDeleted(from, to) : await container.userRepository.findByPage(from, to); (0, Logger_1.logRequest)('Admin users page retrieved successfully', req, res, { from, to, count: result.users.length, total: result.totalCount, includeDeleted }); res.json(result); } catch (error) { (0, Logger_1.logError)('Admin get users by page endpoint error', error, req, res); res.status(500).json({ error: 'Internal server error' }); } }); // Get user by ID including soft-deleted ones router.get('/users/:userId', AuthMiddleware_1.adminRequired, ValidationMiddleware_1.ValidationMiddleware.validateUUIDFormat(['userId']), async (req, res) => { try { const targetUserId = req.params.userId; const includeDeleted = req.query.includeDeleted === 'true'; (0, Logger_1.logRequest)('Admin get user by id endpoint accessed', req, res, { targetUserId, includeDeleted }); const user = includeDeleted ? await container.userRepository.findByIdIncludingDeleted(targetUserId) : await container.userRepository.findById(targetUserId); if (!user) { (0, Logger_1.logWarning)('User not found', { targetUserId, includeDeleted }, req, res); return res.status(404).json({ error: 'User not found' }); } (0, Logger_1.logRequest)('Admin user retrieved successfully', req, res, { targetUserId, username: user.username, includeDeleted }); res.json(user); } catch (error) { (0, Logger_1.logError)('Admin get user by id endpoint error', error, req, res); res.status(500).json({ error: 'Internal server error' }); } }); // Search users including soft-deleted ones router.get('/users/search/:searchTerm', AuthMiddleware_1.adminRequired, ValidationMiddleware_1.ValidationMiddleware.validateStringLength({ searchTerm: { min: 2, max: 100 } }), async (req, res) => { try { const { searchTerm } = req.params; const includeDeleted = req.query.includeDeleted === 'true'; (0, Logger_1.logRequest)('Admin search users endpoint accessed', req, res, { searchTerm, includeDeleted }); const users = includeDeleted ? await container.userRepository.searchIncludingDeleted(searchTerm) : await container.userRepository.search(searchTerm); (0, Logger_1.logRequest)('Admin user search completed', req, res, { searchTerm, resultCount: Array.isArray(users) ? users.length : (users.totalCount || 0), includeDeleted }); res.json(users); } catch (error) { (0, Logger_1.logError)('Admin search users endpoint error', error, req, res); res.status(500).json({ error: 'Internal server error' }); } }); // Update any user (admin only) router.patch('/users/:userId', AuthMiddleware_1.adminRequired, ValidationMiddleware_1.ValidationMiddleware.validateUUIDFormat(['userId']), async (req, res) => { try { const targetUserId = req.params.userId; const adminUserId = req.user.userId; (0, Logger_1.logRequest)('Admin update user endpoint accessed', req, res, { adminUserId, targetUserId, fieldsToUpdate: Object.keys(req.body) }); const result = await container.updateUserCommandHandler.execute({ id: targetUserId, ...req.body }); if (!result) { return res.status(404).json({ error: 'User not found' }); } (0, Logger_1.logRequest)('User updated by admin', req, res, { adminUserId, targetUserId, username: result.username }); res.json(result); } catch (error) { (0, Logger_1.logError)('Admin update user endpoint error', error, req, res); if (error instanceof Error) { if (error.message.includes('already exists')) { return res.status(409).json({ error: error.message }); } if (error.message.includes('validation')) { return res.status(400).json({ error: error.message }); } } res.status(500).json({ error: 'Internal server error' }); } }); // Deactivate user (admin only) router.post('/users/:userId/deactivate', AuthMiddleware_1.adminRequired, ValidationMiddleware_1.ValidationMiddleware.validateUUIDFormat(['userId']), async (req, res) => { try { const targetUserId = req.params.userId; const adminUserId = req.user.userId; (0, Logger_1.logRequest)('Deactivate user endpoint accessed', req, res, { adminUserId, targetUserId }); const result = await container.deactivateUserCommandHandler.execute({ id: targetUserId }); if (!result) { return res.status(404).json({ error: 'User not found' }); } (0, Logger_1.logAuth)('User deactivated by admin', targetUserId, { adminUserId }, req, res); res.json({ message: 'User deactivated successfully', user: result }); } catch (error) { (0, Logger_1.logError)('Deactivate user endpoint error', error, req, res); res.status(500).json({ error: 'Internal server error' }); } }); // Delete user (admin only) router.delete('/users/:userId', AuthMiddleware_1.adminRequired, ValidationMiddleware_1.ValidationMiddleware.validateUUIDFormat(['userId']), async (req, res) => { try { const targetUserId = req.params.userId; const adminUserId = req.user.userId; (0, Logger_1.logRequest)('Delete user endpoint accessed', req, res, { adminUserId, targetUserId }); const result = await container.deleteUserCommandHandler.execute({ id: targetUserId }); if (!result) { return res.status(404).json({ error: 'User not found' }); } (0, Logger_1.logAuth)('User deleted by admin', targetUserId, { adminUserId }, req, res); res.json({ message: 'User deleted successfully' }); } catch (error) { (0, Logger_1.logError)('Delete user endpoint error', error, req, res); res.status(500).json({ error: 'Internal server error' }); } }); // ============================================================================= // DECK MANAGEMENT ROUTES // ============================================================================= // Get decks by page (admin only) - RECOMMENDED router.get('/decks/page/:from/:to', AuthMiddleware_1.adminRequired, async (req, res) => { try { const from = parseInt(req.params.from); const to = parseInt(req.params.to); const includeDeleted = req.query.includeDeleted === 'true'; if (isNaN(from) || isNaN(to) || from < 0 || to < from) { return res.status(400).json({ error: 'Invalid page parameters. "from" and "to" must be valid numbers with to >= from >= 0' }); } (0, Logger_1.logRequest)('Admin get decks by page endpoint accessed', req, res, { from, to, includeDeleted }); // For admin, we need to pass admin context to get unrestricted decks const adminUserId = req.user.userId; const result = await container.getDecksByPageQueryHandler.execute({ userId: adminUserId, userOrgId: undefined, isAdmin: true, from, to, includeDeleted }); (0, Logger_1.logRequest)('Admin decks page retrieved successfully', req, res, { from, to, count: result.decks.length, total: result.totalCount, includeDeleted }); res.json(result); } catch (error) { (0, Logger_1.logError)('Admin get decks by page endpoint error', error, req, res); res.status(500).json({ error: 'Internal server error' }); } }); // Get deck by ID including soft-deleted ones router.get('/decks/:id', AuthMiddleware_1.adminRequired, async (req, res) => { try { const { id } = req.params; const includeDeleted = req.query.includeDeleted === 'true'; (0, Logger_1.logRequest)('Admin get deck by id endpoint accessed', req, res, { deckId: id, includeDeleted }); const deck = includeDeleted ? await container.deckRepository.findByIdIncludingDeleted(id) : await container.deckRepository.findById(id); if (!deck) { (0, Logger_1.logWarning)('Deck not found', { deckId: id, includeDeleted }, req, res); return res.status(404).json({ error: 'Deck not found' }); } (0, Logger_1.logRequest)('Admin deck retrieved successfully', req, res, { deckId: id, includeDeleted }); res.json(deck); } catch (error) { (0, Logger_1.logError)('Admin get deck by id endpoint error', error, req, res); res.status(500).json({ error: 'Internal server error' }); } }); // Search decks including soft-deleted ones router.get('/decks/search/:searchTerm', AuthMiddleware_1.adminRequired, async (req, res) => { try { const { searchTerm } = req.params; const includeDeleted = req.query.includeDeleted === 'true'; (0, Logger_1.logRequest)('Admin search decks endpoint accessed', req, res, { searchTerm, includeDeleted }); const decks = includeDeleted ? await container.deckRepository.searchIncludingDeleted(searchTerm) : await container.deckRepository.search(searchTerm); (0, Logger_1.logRequest)('Admin deck search completed', req, res, { searchTerm, resultCount: Array.isArray(decks) ? decks.length : (decks.totalCount || 0), includeDeleted }); res.json(decks); } catch (error) { (0, Logger_1.logError)('Admin search decks endpoint error', error, req, res); res.status(500).json({ error: 'Internal server error' }); } }); // Hard delete deck (admin only) router.delete('/decks/:id/hard', AuthMiddleware_1.adminRequired, async (req, res) => { try { const deckId = req.params.id; (0, Logger_1.logRequest)('Admin hard delete deck endpoint accessed', req, res, { deckId }); const result = await container.deleteDeckCommandHandler.execute({ id: deckId, soft: false }); (0, Logger_1.logRequest)('Admin deck hard delete successful', req, res, { deckId, success: result }); res.json({ success: result }); } catch (error) { (0, Logger_1.logError)('Admin hard delete deck endpoint error', error, req, res); if (error instanceof Error && error.message.includes('not found')) { return res.status(404).json({ error: 'Deck not found' }); } res.status(500).json({ error: 'Internal server error' }); } }); // ============================================================================= // ORGANIZATION MANAGEMENT ROUTES // ============================================================================= // Create organization (admin only) router.post('/organizations', AuthMiddleware_1.adminRequired, async (req, res) => { try { const adminUserId = req.user.userId; (0, Logger_1.logRequest)('Admin create organization endpoint accessed', req, res, { name: req.body.name, adminUserId }); const result = await container.createOrganizationCommandHandler.execute(req.body); AdminBypassService_1.AdminAuditService.logAdminAction('CREATE_ORGANIZATION', adminUserId, { targetType: 'organization', targetId: result.id, operation: 'create', changes: req.body }, req, res); (0, Logger_1.logRequest)('Admin organization created successfully', req, res, { organizationId: result.id, name: req.body.name, adminUserId }); res.json(result); } catch (error) { (0, Logger_1.logError)('Admin create organization endpoint error', error, req, res); if (error instanceof Error && (error.message.includes('duplicate') || error.message.includes('unique constraint'))) { return res.status(409).json({ error: 'Organization with this name already exists' }); } if (error instanceof Error && error.message.includes('validation')) { return res.status(400).json({ error: 'Invalid input data', details: error.message }); } res.status(500).json({ error: 'Internal server error' }); } }); // Update organization (admin only) - NEW ENDPOINT router.patch('/organizations/:id', AuthMiddleware_1.adminRequired, async (req, res) => { try { const organizationId = req.params.id; const adminUserId = req.user.userId; (0, Logger_1.logRequest)('Admin update organization endpoint accessed', req, res, { adminUserId, organizationId, fieldsToUpdate: Object.keys(req.body) }); const result = await container.updateOrganizationCommandHandler.execute({ id: organizationId, ...req.body }); if (!result) { return res.status(404).json({ error: 'Organization not found' }); } AdminBypassService_1.AdminAuditService.logAdminAction('UPDATE_ORGANIZATION', adminUserId, { targetType: 'organization', targetId: organizationId, operation: 'update', changes: req.body, sensitive: req.body.maxOrganizationalDecks !== undefined }, req, res); (0, Logger_1.logRequest)('Organization updated by admin', req, res, { adminUserId, organizationId, organizationName: result.name }); res.json(result); } catch (error) { (0, Logger_1.logError)('Admin update organization endpoint error', error, req, res); if (error instanceof Error) { if (error.message.includes('already exists')) { return res.status(409).json({ error: error.message }); } if (error.message.includes('validation')) { return res.status(400).json({ error: error.message }); } } res.status(500).json({ error: 'Internal server error' }); } }); // Get organizations by page (admin only) - RECOMMENDED router.get('/organizations/page/:from/:to', AuthMiddleware_1.adminRequired, async (req, res) => { try { const from = parseInt(req.params.from); const to = parseInt(req.params.to); const includeDeleted = req.query.includeDeleted === 'true'; if (isNaN(from) || isNaN(to) || from < 0 || to < from) { return res.status(400).json({ error: 'Invalid page parameters. "from" and "to" must be valid numbers with to >= from >= 0' }); } (0, Logger_1.logRequest)('Admin get organizations by page endpoint accessed', req, res, { from, to, includeDeleted }); const result = await container.getOrganizationsByPageQueryHandler.execute({ from, to, includeDeleted }); (0, Logger_1.logRequest)('Admin organizations page retrieved successfully', req, res, { from, to, count: result.organizations.length, total: result.totalCount, includeDeleted }); res.json(result); } catch (error) { (0, Logger_1.logError)('Admin get organizations by page endpoint error', error, req, res); res.status(500).json({ error: 'Internal server error' }); } }); // Get organization by ID including soft-deleted ones router.get('/organizations/:id', AuthMiddleware_1.adminRequired, async (req, res) => { try { const organizationId = req.params.id; const includeDeleted = req.query.includeDeleted === 'true'; (0, Logger_1.logRequest)('Admin get organization by id endpoint accessed', req, res, { organizationId, includeDeleted }); const organization = includeDeleted ? await container.organizationRepository.findByIdIncludingDeleted(organizationId) : await container.organizationRepository.findById(organizationId); if (!organization) { (0, Logger_1.logWarning)('Organization not found', { organizationId, includeDeleted }, req, res); return res.status(404).json({ error: 'Organization not found' }); } (0, Logger_1.logRequest)('Admin organization retrieved successfully', req, res, { organizationId, includeDeleted }); res.json(organization); } catch (error) { (0, Logger_1.logError)('Admin get organization by id endpoint error', error, req, res); res.status(500).json({ error: 'Internal server error' }); } }); // Search organizations including soft-deleted ones router.get('/organizations/search/:searchTerm', AuthMiddleware_1.adminRequired, async (req, res) => { try { const { searchTerm } = req.params; const includeDeleted = req.query.includeDeleted === 'true'; (0, Logger_1.logRequest)('Admin search organizations endpoint accessed', req, res, { searchTerm, includeDeleted }); const organizations = includeDeleted ? await container.organizationRepository.searchIncludingDeleted(searchTerm) : await container.organizationRepository.search(searchTerm); (0, Logger_1.logRequest)('Admin organization search completed', req, res, { searchTerm, resultCount: Array.isArray(organizations) ? organizations.length : (organizations.totalCount || 0), includeDeleted }); res.json(organizations); } catch (error) { (0, Logger_1.logError)('Admin search organizations endpoint error', error, req, res); res.status(500).json({ error: 'Internal server error' }); } }); // Soft delete organization (admin only) router.delete('/organizations/:id', AuthMiddleware_1.adminRequired, async (req, res) => { try { const organizationId = req.params.id; (0, Logger_1.logRequest)('Admin soft delete organization endpoint accessed', req, res, { organizationId }); const result = await container.deleteOrganizationCommandHandler.execute({ id: organizationId, soft: true }); (0, Logger_1.logRequest)('Admin organization soft delete successful', req, res, { organizationId, success: result }); res.json({ success: result }); } catch (error) { (0, Logger_1.logError)('Admin soft delete organization endpoint error', error, req, res); if (error instanceof Error && error.message.includes('not found')) { return res.status(404).json({ error: 'Organization not found' }); } res.status(500).json({ error: 'Internal server error' }); } }); // Hard delete organization (admin only) router.delete('/organizations/:id/hard', AuthMiddleware_1.adminRequired, async (req, res) => { try { const organizationId = req.params.id; (0, Logger_1.logRequest)('Admin hard delete organization endpoint accessed', req, res, { organizationId }); const result = await container.deleteOrganizationCommandHandler.execute({ id: organizationId, soft: false }); (0, Logger_1.logRequest)('Admin organization hard delete successful', req, res, { organizationId, success: result }); res.json({ success: result }); } catch (error) { (0, Logger_1.logError)('Admin hard delete organization endpoint error', error, req, res); if (error instanceof Error && error.message.includes('not found')) { return res.status(404).json({ error: 'Organization not found' }); } res.status(500).json({ error: 'Internal server error' }); } }); // ============================================================================= // CHAT MANAGEMENT ROUTES // ============================================================================= // Get chats with pagination (RECOMMENDED) router.get('/chats/page/:from/:to', AuthMiddleware_1.adminRequired, async (req, res) => { try { const from = parseInt(req.params.from); const to = parseInt(req.params.to); const includeDeleted = req.query.includeDeleted === 'true'; if (isNaN(from) || isNaN(to) || from < 0 || to < from) { return res.status(400).json({ error: 'Invalid pagination parameters. From and to must be valid numbers with from <= to.' }); } const limit = to - from + 1; if (limit > 100) { return res.status(400).json({ error: 'Page size too large. Maximum 100 records per request.' }); } (0, Logger_1.logRequest)('Admin paginated chats endpoint accessed', req, res, { from, to, includeDeleted }); const result = await container.getChatsByPageQueryHandler.execute({ from, to, includeDeleted }); const response = { chats: result.chats, pagination: { from, to, returned: result.chats.length, totalCount: result.totalCount, includeDeleted } }; (0, Logger_1.logRequest)('Admin chats retrieved successfully', req, res, { returnedChats: result.chats.length, totalCount: result.totalCount, from, to, includeDeleted }); return res.status(200).json(response); } catch (error) { (0, Logger_1.logError)('Error in admin get chats endpoint', error, req, res); return res.status(500).json({ error: 'Internal server error' }); } }); // Get chat by ID including soft-deleted ones router.get('/chats/:id', AuthMiddleware_1.adminRequired, async (req, res) => { try { const { id } = req.params; const includeDeleted = req.query.includeDeleted === 'true'; (0, Logger_1.logRequest)('Admin get chat by id endpoint accessed', req, res, { chatId: id, includeDeleted }); const chat = includeDeleted ? await container.chatRepository.findByIdIncludingDeleted(id) : await container.chatRepository.findById(id); if (!chat) { (0, Logger_1.logWarning)('Chat not found', { chatId: id, includeDeleted }, req, res); return res.status(404).json({ error: 'Chat not found' }); } (0, Logger_1.logRequest)('Admin chat retrieved successfully', req, res, { chatId: id, includeDeleted }); res.json(chat); } catch (error) { (0, Logger_1.logError)('Admin get chat by id endpoint error', error, req, res); res.status(500).json({ error: 'Internal server error' }); } }); // ============================================================================= // CONTACT MANAGEMENT ROUTES // ============================================================================= // Get contacts by page (admin only) - RECOMMENDED (already exists, enhanced) router.get('/contacts/page/:from/:to', AuthMiddleware_1.adminRequired, async (req, res) => { try { const from = parseInt(req.params.from); const to = parseInt(req.params.to); const includeDeleted = req.query.includeDeleted === 'true'; if (isNaN(from) || isNaN(to) || from < 0 || to < from) { return res.status(400).json({ error: 'Invalid page parameters. "from" and "to" must be valid numbers with to >= from >= 0' }); } (0, Logger_1.logRequest)('Admin get contacts by page endpoint accessed', req, res, { from, to, includeDeleted }); const result = includeDeleted ? await container.contactRepository.findByPageIncludingDeleted(from, to) : await container.contactRepository.findByPage(from, to); (0, Logger_1.logRequest)('Admin contacts page retrieved successfully', req, res, { from, to, count: result.contacts.length, total: result.totalCount, includeDeleted }); res.json(result); } catch (error) { (0, Logger_1.logError)('Admin get contacts by page endpoint error', error, req, res); res.status(500).json({ error: 'Internal server error' }); } }); // Get contact by ID (admin only) router.get('/contacts/:id', AuthMiddleware_1.adminRequired, async (req, res) => { try { const contactId = req.params.id; const includeDeleted = req.query.includeDeleted === 'true'; (0, Logger_1.logRequest)('Admin get contact by ID endpoint accessed', req, res, { contactId, includeDeleted }); const result = includeDeleted ? await container.contactRepository.findByIdIncludingDeleted(contactId) : await container.getContactByIdQueryHandler.execute({ id: contactId }); if (!result) { (0, Logger_1.logRequest)('Contact not found', req, res, { contactId, includeDeleted }); return res.status(404).json({ error: 'Contact not found' }); } (0, Logger_1.logRequest)('Admin contact retrieved successfully', req, res, { contactId, includeDeleted }); res.json(result); } catch (error) { (0, Logger_1.logError)('Admin get contact by ID endpoint error', error, req, res); res.status(500).json({ error: 'Internal server error' }); } }); // Search contacts including soft-deleted ones (admin only) router.get('/contacts/search/:searchTerm', AuthMiddleware_1.adminRequired, async (req, res) => { try { const { searchTerm } = req.params; const includeDeleted = req.query.includeDeleted === 'true'; (0, Logger_1.logRequest)('Admin search contacts endpoint accessed', req, res, { searchTerm, includeDeleted }); const contacts = includeDeleted ? await container.contactRepository.searchIncludingDeleted(searchTerm) : await container.contactRepository.search(searchTerm); (0, Logger_1.logRequest)('Admin contact search completed', req, res, { searchTerm, resultCount: contacts.length, includeDeleted }); res.json(contacts); } catch (error) { (0, Logger_1.logError)('Admin search contacts endpoint error', error, req, res); res.status(500).json({ error: 'Internal server error' }); } }); // Respond to contact (admin only) router.put('/contacts/:id/respond', AuthMiddleware_1.adminRequired, async (req, res) => { try { const contactId = req.params.id; const adminUserId = req.user.userId; const { adminResponse, sendEmail, language } = req.body; if (!adminResponse) { return res.status(400).json({ error: 'Admin response is required' }); } // Determine language from body, headers, or default to English let selectedLanguage = language; if (!selectedLanguage) { // Try to get language from Accept-Language header const acceptLanguage = req.headers['accept-language']; // Try to get language from custom headers (common frontend patterns) const regionHeader = req.headers['x-region']; const languageHeader = req.headers['x-language']; const localeHeader = req.headers['x-locale']; selectedLanguage = languageHeader || localeHeader || regionHeader || extractLanguageFromAcceptHeader(acceptLanguage) || 'en'; } // Validate and normalize language parameter if (!['en', 'hu', 'de'].includes(selectedLanguage.toLowerCase())) { selectedLanguage = 'en'; // Fallback to English for unsupported languages } else { selectedLanguage = selectedLanguage.toLowerCase(); } (0, Logger_1.logRequest)('Admin respond to contact endpoint accessed', req, res, { contactId, adminUserId, sendEmail, language: selectedLanguage, headerLanguage: req.headers['accept-language'] || req.headers['x-language'] || 'none' }); // Update contact with response const result = await container.updateContactCommandHandler.execute({ id: contactId, adminResponse, respondedBy: adminUserId }); if (!result) { (0, Logger_1.logWarning)('Contact not found for response', { contactId }, req, res); return res.status(404).json({ error: 'Contact not found' }); } // Send email if requested let emailSent = false; let emailError = null; if (sendEmail === true && adminResponse) { try { await container.contactEmailService.sendResponse({ to: result.email, message: adminResponse, contactId: contactId, adminUserId: adminUserId, contactName: result.name, contactType: result.type, originalMessage: result.txt, language: selectedLanguage }); emailSent = true; (0, Logger_1.logRequest)('Contact response email sent successfully', req, res, { contactId, recipientEmail: result.email, language: selectedLanguage }); } catch (emailErr) { emailError = emailErr instanceof Error ? emailErr.message : 'Email sending failed'; (0, Logger_1.logError)('Contact response email failed', emailErr, req, res); } } AdminBypassService_1.AdminAuditService.logAdminAction('RESPOND_TO_CONTACT', adminUserId, { targetType: 'contact', targetId: contactId, operation: 'update', changes: { adminResponse, sendEmail, language: selectedLanguage }, metadata: { emailSent, emailError } }, req, res); (0, Logger_1.logRequest)('Admin contact response saved successfully', req, res, { contactId, sendEmail, emailSent, language: selectedLanguage }); res.json({ success: true, message: 'Response saved successfully', contact: result, emailSent, emailError: emailSent ? null : emailError }); } catch (error) { (0, Logger_1.logError)('Admin respond to contact endpoint error', error, req, res); if (error instanceof Error && error.message.includes('not found')) { return res.status(404).json({ error: 'Contact not found' }); } if (error instanceof Error && error.message.includes('validation')) { return res.status(400).json({ error: 'Invalid input data', details: error.message }); } res.status(500).json({ error: 'Internal server error' }); } }); // Resend contact email (admin only) - NEW ENDPOINT router.post('/contacts/:id/resend-email', AuthMiddleware_1.adminRequired, async (req, res) => { try { const contactId = req.params.id; const adminUserId = req.user.userId; const { language } = req.body; (0, Logger_1.logRequest)('Admin resend contact email endpoint accessed', req, res, { contactId, adminUserId, language }); // Get contact details const contact = await container.getContactByIdQueryHandler.execute({ id: contactId }); if (!contact) { return res.status(404).json({ error: 'Contact not found' }); } if (!contact.adminResponse) { return res.status(400).json({ error: 'No admin response found to resend' }); } const selectedLanguage = language || 'en'; try { await container.contactEmailService.sendResponse({ to: contact.email, message: contact.adminResponse, contactId: contactId, adminUserId: adminUserId, contactName: contact.name, contactType: contact.type, originalMessage: contact.txt, language: selectedLanguage }); AdminBypassService_1.AdminAuditService.logAdminAction('RESEND_CONTACT_EMAIL', adminUserId, { targetType: 'contact', targetId: contactId, operation: 'create', metadata: { language: selectedLanguage, action: 'resend' } }, req, res); (0, Logger_1.logRequest)('Contact email resent successfully', req, res, { contactId, recipientEmail: contact.email, language: selectedLanguage }); res.json({ success: true, message: 'Email resent successfully' }); } catch (emailErr) { (0, Logger_1.logError)('Contact email resend failed', emailErr, req, res); res.status(500).json({ error: 'Failed to resend email', details: emailErr instanceof Error ? emailErr.message : 'Unknown error' }); } } catch (error) { (0, Logger_1.logError)('Admin resend contact email endpoint error', error, req, res); res.status(500).json({ error: 'Internal server error' }); } }); // Soft delete contact (admin only) - NEW ENDPOINT router.delete('/contacts/:id', AuthMiddleware_1.adminRequired, async (req, res) => { try { const contactId = req.params.id; const adminUserId = req.user.userId; (0, Logger_1.logRequest)('Admin soft delete contact endpoint accessed', req, res, { contactId, adminUserId }); const result = await container.deleteContactCommandHandler.execute({ id: contactId, hard: false }); AdminBypassService_1.AdminAuditService.logAdminAction('SOFT_DELETE_CONTACT', adminUserId, { targetType: 'contact', targetId: contactId, operation: 'update' }, req, res); (0, Logger_1.logAuth)('Contact soft deleted by admin', contactId, { adminUserId }, req, res); res.json({ success: result }); } catch (error) { (0, Logger_1.logError)('Admin soft delete contact endpoint error', error, req, res); if (error instanceof Error && error.message.includes('not found')) { return res.status(404).json({ error: 'Contact not found' }); } res.status(500).json({ error: 'Internal server error' }); } }); // Hard delete contact (admin only) - NEW ENDPOINT router.delete('/contacts/:id/hard', AuthMiddleware_1.adminRequired, async (req, res) => { try { const contactId = req.params.id; const adminUserId = req.user.userId; (0, Logger_1.logRequest)('Admin hard delete contact endpoint accessed', req, res, { contactId, adminUserId }); const result = await container.deleteContactCommandHandler.execute({ id: contactId, hard: true }); AdminBypassService_1.AdminAuditService.logAdminAction('HARD_DELETE_CONTACT', adminUserId, { targetType: 'contact', targetId: contactId, operation: 'delete', sensitive: true }, req, res); (0, Logger_1.logAuth)('Contact hard deleted by admin', contactId, { adminUserId }, req, res); res.json({ success: result }); } catch (error) { (0, Logger_1.logError)('Admin hard delete contact endpoint error', error, req, res); if (error instanceof Error && error.message.includes('not found')) { return res.status(404).json({ error: 'Contact not found' }); } res.status(500).json({ error: 'Internal server error' }); } }); // ============================================================================= // DECK IMPORT/EXPORT ROUTES (ADMIN) // ============================================================================= // Import deck from JSON file (unencrypted, admin only) router.post('/decks/import', AuthMiddleware_1.adminRequired, upload.single('file'), async (req, res) => { try { if (!req.file) { return res.status(400).json({ error: 'No file uploaded' }); } const userId = req.user.userId; const fileContent = req.file.buffer.toString('utf-8'); (0, Logger_1.logRequest)('Admin deck import from JSON endpoint accessed', req, res, { fileName: req.file.originalname }); let jsonData; try { jsonData = JSON.parse(fileContent); } catch (parseError) { return res.status(400).json({ error: 'Invalid JSON format' }); } // For admin import, we need to specify both target user and admin user // Let's assume the deck will be owned by the admin user doing the import const result = await container.deckImportExportService.adminImportFromJson(jsonData, userId, userId); (0, Logger_1.logRequest)('Admin deck import successful', req, res, { deckId: result.id, fileName: req.file.originalname }); res.json({ success: true, message: 'Deck imported successfully', deckId: result.id }); } catch (error) { (0, Logger_1.logError)('Admin deck import from JSON error', error, req, res); if (error instanceof Error && error.message.includes('Invalid')) { res.status(400).json({ error: 'Invalid deck data structure' }); } else { res.status(500).json({ error: 'Internal server error' }); } } }); // Export deck as JSON (unencrypted, admin only) router.get('/decks/:deckId/export', AuthMiddleware_1.adminRequired, async (req, res) => { try { const { deckId } = req.params; (0, Logger_1.logRequest)('Admin deck export as JSON endpoint accessed', req, res, { deckId }); const deck = await container.deckRepository.findById(deckId); if (!deck) { (0, Logger_1.logWarning)('Deck not found for export', { deckId }, req, res); return res.status(404).json({ error: 'Deck not found' }); } (0, Logger_1.logRequest)('Admin deck export successful', req, res, { deckId, deckName: deck.name }); // Return deck as JSON for admin export res.setHeader('Content-Type', 'application/json'); res.setHeader('Content-Disposition', `attachment; filename="${deck.name || 'deck'}.json"`); res.json(deck); } catch (error) { (0, Logger_1.logError)('Admin deck export as JSON error', error, req, res); res.status(500).json({ error: 'Internal server error' }); } }); exports.default = router; //# sourceMappingURL=adminRouter.js.map