Files
SerpentRace/SerpentRace_Backend/dist/Api/routers/adminRouter.js
T

932 lines
42 KiB
JavaScript

"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