https://project.mdnd-it.cc/work_packages/94
This commit is contained in:
2025-08-23 04:25:28 +02:00
parent 725516ad6c
commit 19cfa031d0
25823 changed files with 1095587 additions and 2801760 deletions
+10
View File
@@ -0,0 +1,10 @@
declare global {
namespace Express {
interface Request {
file?: Express.Multer.File;
}
}
}
declare const router: import("express-serve-static-core").Router;
export default router;
//# sourceMappingURL=adminRouter.d.ts.map
@@ -0,0 +1 @@
{"version":3,"file":"adminRouter.d.ts","sourceRoot":"","sources":["../../../src/Api/routers/adminRouter.ts"],"names":[],"mappings":"AAYA,OAAO,CAAC,MAAM,CAAC;IACX,UAAU,OAAO,CAAC;QACd,UAAU,OAAO;YACb,IAAI,CAAC,EAAE,OAAO,CAAC,MAAM,CAAC,IAAI,CAAC;SAC9B;KACJ;CACJ;AAED,QAAA,MAAM,MAAM,4CAAmB,CAAC;AA4kChC,eAAe,MAAM,CAAC"}
+932
View File
@@ -0,0 +1,932 @@
"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
File diff suppressed because one or more lines are too long
+3
View File
@@ -0,0 +1,3 @@
declare const chatRouter: import("express-serve-static-core").Router;
export default chatRouter;
//# sourceMappingURL=chatRouter.d.ts.map
@@ -0,0 +1 @@
{"version":3,"file":"chatRouter.d.ts","sourceRoot":"","sources":["../../../src/Api/routers/chatRouter.ts"],"names":[],"mappings":"AAOA,QAAA,MAAM,UAAU,4CAAmB,CAAC;AAuRpC,eAAe,UAAU,CAAC"}
+231
View File
@@ -0,0 +1,231 @@
"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 AuthMiddleware_1 = require("../../Application/Services/AuthMiddleware");
const DIContainer_1 = require("../../Application/Services/DIContainer");
const ErrorResponseService_1 = require("../../Application/Services/ErrorResponseService");
const ValidationMiddleware_1 = require("../../Application/Services/ValidationMiddleware");
const Logger_1 = require("../../Application/Services/Logger");
const chatRouter = express_1.default.Router();
// Get user's chats
chatRouter.get('/user-chats', AuthMiddleware_1.authRequired, async (req, res) => {
try {
const userId = req.user.userId;
const includeArchived = req.query.includeArchived === 'true';
(0, Logger_1.logRequest)('Get user chats endpoint accessed', req, res, { userId, includeArchived });
const chats = await DIContainer_1.container.getUserChatsQueryHandler.execute({
userId,
includeArchived
});
(0, Logger_1.logRequest)('User chats retrieved successfully', req, res, {
userId,
chatCount: chats.length
});
res.json(chats);
}
catch (error) {
(0, Logger_1.logError)('Get user chats endpoint error', error, req, res);
return ErrorResponseService_1.ErrorResponseService.sendInternalServerError(res);
}
});
// Get chat history
chatRouter.get('/history/:chatId', AuthMiddleware_1.authRequired, ValidationMiddleware_1.ValidationMiddleware.validateUUIDFormat(['chatId']), async (req, res) => {
try {
const userId = req.user.userId;
const chatId = req.params.chatId;
(0, Logger_1.logRequest)('Get chat history endpoint accessed', req, res, { userId, chatId });
const history = await DIContainer_1.container.getChatHistoryQueryHandler.execute({
chatId,
userId
});
if (!history) {
(0, Logger_1.logWarning)('Chat history not found or unauthorized', { userId, chatId }, req, res);
return ErrorResponseService_1.ErrorResponseService.sendNotFound(res, 'Chat not found or unauthorized');
}
(0, Logger_1.logRequest)('Chat history retrieved successfully', req, res, {
userId,
chatId,
messageCount: history.messages.length,
isArchived: history.isArchived
});
res.json(history);
}
catch (error) {
(0, Logger_1.logError)('Get chat history endpoint error', error, req, res);
return ErrorResponseService_1.ErrorResponseService.sendInternalServerError(res);
}
});
// Create new chat (direct/group)
chatRouter.post('/create', AuthMiddleware_1.authRequired, ValidationMiddleware_1.ValidationMiddleware.combine([
ValidationMiddleware_1.ValidationMiddleware.validateRequiredFields(['type', 'userIds']),
ValidationMiddleware_1.ValidationMiddleware.validateAllowedValues({ type: ['direct', 'group'] }),
ValidationMiddleware_1.ValidationMiddleware.validateNonEmptyArrays(['userIds'])
]), async (req, res) => {
try {
const userId = req.user.userId;
const { type, name, userIds } = req.body;
(0, Logger_1.logRequest)('Create chat endpoint accessed', req, res, {
userId,
type,
targetUserCount: userIds?.length || 0
});
if (type === 'group' && !name?.trim()) {
return ErrorResponseService_1.ErrorResponseService.sendBadRequest(res, 'Group name is required');
}
const chat = await DIContainer_1.container.createChatCommandHandler.execute({
type,
name: name?.trim(),
createdBy: userId,
userIds
});
if (!chat) {
return ErrorResponseService_1.ErrorResponseService.sendBadRequest(res, 'Failed to create chat');
}
(0, Logger_1.logRequest)('Chat created successfully', req, res, {
userId,
chatId: chat.id,
chatType: chat.type
});
res.json({
id: chat.id,
type: chat.type,
name: chat.name,
users: chat.users,
messages: chat.messages
});
}
catch (error) {
(0, Logger_1.logError)('Create chat endpoint error', error, req, res);
if (error instanceof Error) {
if (error.message.includes('Premium subscription required')) {
return ErrorResponseService_1.ErrorResponseService.sendForbidden(res, 'Premium subscription required to create groups');
}
if (error.message.includes('not found')) {
return ErrorResponseService_1.ErrorResponseService.sendNotFound(res, 'One or more users not found');
}
}
return ErrorResponseService_1.ErrorResponseService.sendInternalServerError(res);
}
});
// Send message (REST endpoint - mainly for testing, real messaging is via WebSocket)
chatRouter.post('/message', AuthMiddleware_1.authRequired, ValidationMiddleware_1.ValidationMiddleware.combine([
ValidationMiddleware_1.ValidationMiddleware.validateRequiredFields(['chatId', 'message']),
ValidationMiddleware_1.ValidationMiddleware.validateUUIDFormat(['chatId']),
ValidationMiddleware_1.ValidationMiddleware.validateStringLength({ message: { min: 1, max: 2000 } })
]), async (req, res) => {
try {
const userId = req.user.userId;
const { chatId, message } = req.body;
(0, Logger_1.logRequest)('Send message endpoint accessed', req, res, {
userId,
chatId,
messageLength: message?.length || 0
});
const sentMessage = await DIContainer_1.container.sendMessageCommandHandler.execute({
chatId,
userId,
message
});
if (!sentMessage) {
return ErrorResponseService_1.ErrorResponseService.sendBadRequest(res, 'Failed to send message');
}
(0, Logger_1.logRequest)('Message sent successfully', req, res, {
userId,
chatId,
messageId: sentMessage.id
});
res.json(sentMessage);
}
catch (error) {
(0, Logger_1.logError)('Send message endpoint error', error, req, res);
if (error instanceof Error) {
if (error.message.includes('Chat not found')) {
return ErrorResponseService_1.ErrorResponseService.sendNotFound(res, 'Chat not found');
}
if (error.message.includes('not a member')) {
return ErrorResponseService_1.ErrorResponseService.sendForbidden(res, 'Not authorized to send messages to this chat');
}
if (error.message.includes('non-empty string')) {
return ErrorResponseService_1.ErrorResponseService.sendBadRequest(res, 'Message must be a non-empty string');
}
}
return ErrorResponseService_1.ErrorResponseService.sendInternalServerError(res);
}
});
// Archive chat manually
chatRouter.post('/archive/:chatId', AuthMiddleware_1.authRequired, ValidationMiddleware_1.ValidationMiddleware.validateUUIDFormat(['chatId']), async (req, res) => {
try {
const userId = req.user.userId;
const chatId = req.params.chatId;
(0, Logger_1.logRequest)('Archive chat endpoint accessed', req, res, { userId, chatId });
// Check if user has access to this chat
const chat = await DIContainer_1.container.chatRepository.findById(chatId);
if (!chat) {
return ErrorResponseService_1.ErrorResponseService.sendNotFound(res, 'Chat not found');
}
if (!chat.users.includes(userId)) {
return ErrorResponseService_1.ErrorResponseService.sendForbidden(res, 'Not authorized to archive this chat');
}
const success = await DIContainer_1.container.archiveChatCommandHandler.execute({ chatId });
if (!success) {
return ErrorResponseService_1.ErrorResponseService.sendBadRequest(res, 'Failed to archive chat');
}
(0, Logger_1.logRequest)('Chat archived successfully', req, res, { userId, chatId });
res.json({ success: true, message: 'Chat archived successfully' });
}
catch (error) {
(0, Logger_1.logError)('Archive chat endpoint error', error, req, res);
return ErrorResponseService_1.ErrorResponseService.sendInternalServerError(res);
}
});
// Restore chat from archive
chatRouter.post('/restore/:chatId', AuthMiddleware_1.authRequired, ValidationMiddleware_1.ValidationMiddleware.validateUUIDFormat(['chatId']), async (req, res) => {
try {
const userId = req.user.userId;
const chatId = req.params.chatId;
(0, Logger_1.logRequest)('Restore chat endpoint accessed', req, res, { userId, chatId });
// Check if user has access to this archived chat
const archive = await DIContainer_1.container.chatArchiveRepository.findByChatId(chatId);
const userArchive = archive.find((a) => a.participants.includes(userId));
if (!userArchive) {
return ErrorResponseService_1.ErrorResponseService.sendNotFound(res, 'Archived chat not found or unauthorized');
}
const success = await DIContainer_1.container.restoreChatCommandHandler.execute({ chatId });
if (!success) {
return ErrorResponseService_1.ErrorResponseService.sendBadRequest(res, 'Failed to restore chat (game chats cannot be restored)');
}
(0, Logger_1.logRequest)('Chat restored successfully', req, res, { userId, chatId });
res.json({ success: true, message: 'Chat restored successfully' });
}
catch (error) {
(0, Logger_1.logError)('Restore chat endpoint error', error, req, res);
return ErrorResponseService_1.ErrorResponseService.sendInternalServerError(res);
}
});
// Get archived chats for a game
chatRouter.get('/archived/game/:gameId', AuthMiddleware_1.authRequired, ValidationMiddleware_1.ValidationMiddleware.validateUUIDFormat(['gameId']), async (req, res) => {
try {
const userId = req.user.userId;
const gameId = req.params.gameId;
(0, Logger_1.logRequest)('Get archived game chats endpoint accessed', req, res, { userId, gameId });
const archivedChats = await DIContainer_1.container.getArchivedChatsQueryHandler.execute({
userId,
gameId
});
(0, Logger_1.logRequest)('Archived game chats retrieved successfully', req, res, {
userId,
gameId,
chatCount: archivedChats.length
});
res.json(archivedChats);
}
catch (error) {
(0, Logger_1.logError)('Get archived game chats endpoint error', error, req, res);
return ErrorResponseService_1.ErrorResponseService.sendInternalServerError(res);
}
});
exports.default = chatRouter;
//# sourceMappingURL=chatRouter.js.map
File diff suppressed because one or more lines are too long
@@ -0,0 +1,3 @@
declare const contactRouter: import("express-serve-static-core").Router;
export default contactRouter;
//# sourceMappingURL=contactRouter.d.ts.map
@@ -0,0 +1 @@
{"version":3,"file":"contactRouter.d.ts","sourceRoot":"","sources":["../../../src/Api/routers/contactRouter.ts"],"names":[],"mappings":"AAKA,QAAA,MAAM,aAAa,4CAAW,CAAC;AA+C/B,eAAe,aAAa,CAAC"}
+46
View File
@@ -0,0 +1,46 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const express_1 = require("express");
const DIContainer_1 = require("../../Application/Services/DIContainer");
const Logger_1 = require("../../Application/Services/Logger");
const ContactAggregate_1 = require("../../Domain/Contact/ContactAggregate");
const contactRouter = (0, express_1.Router)();
// Public endpoint - anyone can create a contact
contactRouter.post('/', async (req, res) => {
try {
// Get user ID if authenticated (optional)
const userId = req.user?.userId || null;
const { name, email, type, txt } = req.body;
// Validate required fields
if (!name || !email || type === undefined || !txt) {
return res.status(400).json({
error: 'Missing required fields: name, email, type, and txt are required'
});
}
// Validate type
if (!Object.values(ContactAggregate_1.ContactType).includes(Number(type))) {
return res.status(400).json({
error: 'Invalid contact type. Must be one of: 0 (Bug), 1 (Problem), 2 (Question), 3 (Sales), 4 (Other)'
});
}
(0, Logger_1.logRequest)('Create contact endpoint accessed', req, res, { name, email, type, userId });
const result = await DIContainer_1.container.createContactCommandHandler.execute({
name,
email,
userid: userId,
type: Number(type),
txt
});
(0, Logger_1.logRequest)('Contact created successfully', req, res, { contactId: result.id, name, email, type });
res.status(201).json(result);
}
catch (error) {
(0, Logger_1.logError)('Create contact endpoint error', error, req, res);
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' });
}
});
exports.default = contactRouter;
//# sourceMappingURL=contactRouter.js.map
@@ -0,0 +1 @@
{"version":3,"file":"contactRouter.js","sourceRoot":"","sources":["../../../src/Api/routers/contactRouter.ts"],"names":[],"mappings":";;AAAA,qCAAiC;AACjC,wEAAmE;AACnE,8DAAyE;AACzE,4EAAoE;AAEpE,MAAM,aAAa,GAAG,IAAA,gBAAM,GAAE,CAAC;AAE/B,gDAAgD;AAChD,aAAa,CAAC,IAAI,CAAC,GAAG,EAAE,KAAK,EAAE,GAAG,EAAE,GAAG,EAAE,EAAE;IAC1C,IAAI,CAAC;QACJ,0CAA0C;QAC1C,MAAM,MAAM,GAAI,GAAW,CAAC,IAAI,EAAE,MAAM,IAAI,IAAI,CAAC;QAEjD,MAAM,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,GAAG,EAAE,GAAG,GAAG,CAAC,IAAI,CAAC;QAE5C,2BAA2B;QAC3B,IAAI,CAAC,IAAI,IAAI,CAAC,KAAK,IAAI,IAAI,KAAK,SAAS,IAAI,CAAC,GAAG,EAAE,CAAC;YACnD,OAAO,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC;gBAC3B,KAAK,EAAE,kEAAkE;aACzE,CAAC,CAAC;QACJ,CAAC;QAED,gBAAgB;QAChB,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,8BAAW,CAAC,CAAC,QAAQ,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,EAAE,CAAC;YACxD,OAAO,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC;gBAC3B,KAAK,EAAE,gGAAgG;aACvG,CAAC,CAAC;QACJ,CAAC;QAED,IAAA,mBAAU,EAAC,kCAAkC,EAAE,GAAG,EAAE,GAAG,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC,CAAC;QAExF,MAAM,MAAM,GAAG,MAAM,uBAAS,CAAC,2BAA2B,CAAC,OAAO,CAAC;YAClE,IAAI;YACJ,KAAK;YACL,MAAM,EAAE,MAAM;YACd,IAAI,EAAE,MAAM,CAAC,IAAI,CAAC;YAClB,GAAG;SACH,CAAC,CAAC;QAEH,IAAA,mBAAU,EAAC,8BAA8B,EAAE,GAAG,EAAE,GAAG,EAAE,EAAE,SAAS,EAAE,MAAM,CAAC,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;QAClG,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;IAC9B,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QAChB,IAAA,iBAAQ,EAAC,+BAA+B,EAAE,KAAc,EAAE,GAAG,EAAE,GAAG,CAAC,CAAC;QAEpE,IAAI,KAAK,YAAY,KAAK,IAAI,KAAK,CAAC,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAC,EAAE,CAAC;YACpE,OAAO,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,oBAAoB,EAAE,OAAO,EAAE,KAAK,CAAC,OAAO,EAAE,CAAC,CAAC;QACtF,CAAC;QAED,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,uBAAuB,EAAE,CAAC,CAAC;IAC1D,CAAC;AACF,CAAC,CAAC,CAAC;AAEH,kBAAe,aAAa,CAAC"}
@@ -0,0 +1,10 @@
declare global {
namespace Express {
interface Request {
file?: Express.Multer.File;
}
}
}
declare const router: import("express-serve-static-core").Router;
export default router;
//# sourceMappingURL=deckImportExportRouter.d.ts.map
@@ -0,0 +1 @@
{"version":3,"file":"deckImportExportRouter.d.ts","sourceRoot":"","sources":["../../../src/Api/routers/deckImportExportRouter.ts"],"names":[],"mappings":"AAOA,OAAO,CAAC,MAAM,CAAC;IACX,UAAU,OAAO,CAAC;QACd,UAAU,OAAO;YACb,IAAI,CAAC,EAAE,OAAO,CAAC,MAAM,CAAC,IAAI,CAAC;SAC9B;KACJ;CACJ;AAED,QAAA,MAAM,MAAM,4CAAmB,CAAC;AA4GhC,eAAe,MAAM,CAAC"}
@@ -0,0 +1,106 @@
"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 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'));
}
}
});
// Export deck to .spr file (encrypted) - users can only export their own decks
router.get('/export/:deckId', AuthMiddleware_1.authRequired, async (req, res) => {
try {
const { deckId } = req.params;
const userId = req.user.userId;
(0, Logger_1.logRequest)('Export deck endpoint accessed', req, res, { deckId, userId });
// Check if user owns the deck
const deck = await container.deckRepository.findById(deckId);
if (!deck) {
(0, Logger_1.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) {
(0, Logger_1.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"`);
(0, Logger_1.logRequest)('Deck exported successfully', req, res, {
deckId,
userId,
deckName: deck.name,
fileSize: sprData.length
});
res.send(sprData);
}
catch (error) {
(0, Logger_1.logError)('Export deck endpoint error', 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', AuthMiddleware_1.authRequired, upload.single('file'), async (req, res) => {
try {
const userId = req.user.userId;
(0, Logger_1.logRequest)('Import deck endpoint accessed', req, res, {
userId,
hasFile: !!req.file,
fileName: req.file?.originalname,
fileSize: req.file?.size
});
if (!req.file) {
(0, Logger_1.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);
(0, Logger_1.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) {
(0, Logger_1.logError)('Import deck endpoint error', 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' });
}
}
});
exports.default = router;
//# sourceMappingURL=deckImportExportRouter.js.map
@@ -0,0 +1 @@
{"version":3,"file":"deckImportExportRouter.js","sourceRoot":"","sources":["../../../src/Api/routers/deckImportExportRouter.ts"],"names":[],"mappings":"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"}
+3
View File
@@ -0,0 +1,3 @@
declare const deckRouter: import("express-serve-static-core").Router;
export default deckRouter;
//# sourceMappingURL=deckRouter.d.ts.map
@@ -0,0 +1 @@
{"version":3,"file":"deckRouter.d.ts","sourceRoot":"","sources":["../../../src/Api/routers/deckRouter.ts"],"names":[],"mappings":"AAQA,QAAA,MAAM,UAAU,4CAAW,CAAC;AAwL5B,eAAe,UAAU,CAAC"}
+162
View File
@@ -0,0 +1,162 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const express_1 = require("express");
const AuthMiddleware_1 = require("../../Application/Services/AuthMiddleware");
const DIContainer_1 = require("../../Application/Services/DIContainer");
const Generalsearch_1 = require("../../Application/Search/Generalsearch");
const Logger_1 = require("../../Application/Services/Logger");
const deckRouter = (0, express_1.Router)();
// Create search service that isn't in the container yet
const searchService = new Generalsearch_1.GeneralSearchService(DIContainer_1.container.userRepository, DIContainer_1.container.organizationRepository, DIContainer_1.container.deckRepository);
// Authenticated routes - Get decks with pagination (RECOMMENDED)
deckRouter.get('/page/:from/:to', AuthMiddleware_1.authRequired, async (req, res) => {
try {
const userId = req.user.userId;
const userOrgId = req.user.orgId;
const isAdmin = req.user.authLevel === 1;
const from = parseInt(req.params.from);
const to = parseInt(req.params.to);
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)('Get decks by page endpoint accessed', req, res, {
userId,
userOrgId,
isAdmin,
from,
to
});
// Use paginated query handler for memory efficiency
const result = await DIContainer_1.container.getDecksByPageQueryHandler.execute({
userId,
userOrgId,
isAdmin,
from,
to
});
(0, Logger_1.logRequest)('Get decks page completed successfully', req, res, {
userId,
from,
to,
returnedCount: result.decks.length,
totalCount: result.totalCount
});
res.json(result);
}
catch (error) {
(0, Logger_1.logError)('Get decks by page endpoint error', error, req, res);
res.status(500).json({ error: 'Internal server error' });
}
});
deckRouter.post('/', AuthMiddleware_1.authRequired, async (req, res) => {
try {
const userId = req.user.userId;
(0, Logger_1.logRequest)('Create deck endpoint accessed', req, res, { name: req.body.name, userId });
const result = await DIContainer_1.container.createDeckCommandHandler.execute(req.body);
(0, Logger_1.logRequest)('Deck created successfully', req, res, { deckId: result.id, name: req.body.name, userId });
res.json(result);
}
catch (error) {
(0, Logger_1.logError)('Create deck endpoint error', error, req, res);
if (error instanceof Error && (error.message.includes('duplicate') || error.message.includes('unique constraint'))) {
return res.status(409).json({ error: 'Deck 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' });
}
});
deckRouter.get('/search', AuthMiddleware_1.authRequired, async (req, res) => {
try {
const { q: query, limit, offset } = req.query;
(0, Logger_1.logRequest)('Search decks endpoint accessed', req, res, { query, limit, offset });
if (!query || typeof query !== 'string') {
(0, Logger_1.logWarning)('Deck search attempted without query', { query, hasQuery: !!query }, req, res);
return res.status(400).json({ error: 'Search query is required' });
}
const searchQuery = {
query: query.trim(),
limit: limit ? parseInt(limit) : 20,
offset: offset ? parseInt(offset) : 0
};
// Validate pagination parameters
if (searchQuery.limit < 1 || searchQuery.limit > 100) {
(0, Logger_1.logWarning)('Invalid deck search limit parameter', { limit: searchQuery.limit }, req, res);
return res.status(400).json({ error: 'Limit must be between 1 and 100' });
}
if (searchQuery.offset < 0) {
(0, Logger_1.logWarning)('Invalid deck search offset parameter', { offset: searchQuery.offset }, req, res);
return res.status(400).json({ error: 'Offset must be non-negative' });
}
const result = await searchService.searchFromUrl(req.originalUrl, searchQuery);
(0, Logger_1.logRequest)('Deck search completed successfully', req, res, {
query: searchQuery.query,
resultCount: Array.isArray(result) ? result.length : 0
});
res.json(result);
}
catch (error) {
(0, Logger_1.logError)('Search decks endpoint error', error, req, res);
res.status(500).json({ error: 'Internal server error' });
}
});
deckRouter.get('/:id', AuthMiddleware_1.authRequired, async (req, res) => {
try {
const deckId = req.params.id;
(0, Logger_1.logRequest)('Get deck by id endpoint accessed', req, res, { deckId });
const result = await DIContainer_1.container.getDeckByIdQueryHandler.execute({ id: deckId });
if (!result) {
(0, Logger_1.logWarning)('Deck not found', { deckId }, req, res);
return res.status(404).json({ error: 'Deck not found' });
}
(0, Logger_1.logRequest)('Deck retrieved successfully', req, res, { deckId });
res.json(result);
}
catch (error) {
(0, Logger_1.logError)('Get deck by id endpoint error', error, req, res);
res.status(500).json({ error: 'Internal server error' });
}
});
deckRouter.put('/:id', AuthMiddleware_1.authRequired, async (req, res) => {
try {
const deckId = req.params.id;
const userId = req.user.userId;
(0, Logger_1.logRequest)('Update deck endpoint accessed', req, res, { deckId, userId, updateFields: Object.keys(req.body) });
const result = await DIContainer_1.container.updateDeckCommandHandler.execute({ id: deckId, ...req.body });
(0, Logger_1.logRequest)('Deck updated successfully', req, res, { deckId, userId });
res.json(result);
}
catch (error) {
(0, Logger_1.logError)('Update deck endpoint error', error, req, res);
if (error instanceof Error && error.message.includes('not found')) {
return res.status(404).json({ error: 'Deck not found' });
}
if (error instanceof Error && (error.message.includes('duplicate') || error.message.includes('unique constraint'))) {
return res.status(409).json({ error: 'Deck 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' });
}
});
deckRouter.delete('/:id', AuthMiddleware_1.authRequired, async (req, res) => {
try {
const deckId = req.params.id;
const userId = req.user.userId;
(0, Logger_1.logRequest)('Soft delete deck endpoint accessed', req, res, { deckId, userId });
const result = await DIContainer_1.container.deleteDeckCommandHandler.execute({ id: deckId, soft: true });
(0, Logger_1.logRequest)('Deck soft delete successful', req, res, { deckId, userId, success: result });
res.json({ success: result });
}
catch (error) {
(0, Logger_1.logError)('Soft 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' });
}
});
exports.default = deckRouter;
//# sourceMappingURL=deckRouter.js.map
File diff suppressed because one or more lines are too long
@@ -0,0 +1,3 @@
declare const organizationRouter: import("express-serve-static-core").Router;
export default organizationRouter;
//# sourceMappingURL=organizationRouter.d.ts.map
@@ -0,0 +1 @@
{"version":3,"file":"organizationRouter.d.ts","sourceRoot":"","sources":["../../../src/Api/routers/organizationRouter.ts"],"names":[],"mappings":"AAQA,QAAA,MAAM,kBAAkB,4CAAW,CAAC;AAmMpC,eAAe,kBAAkB,CAAC"}
@@ -0,0 +1,179 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const express_1 = require("express");
const AuthMiddleware_1 = require("../../Application/Services/AuthMiddleware");
const DIContainer_1 = require("../../Application/Services/DIContainer");
const ErrorResponseService_1 = require("../../Application/Services/ErrorResponseService");
const Generalsearch_1 = require("../../Application/Search/Generalsearch");
const Logger_1 = require("../../Application/Services/Logger");
const organizationRouter = (0, express_1.Router)();
// Create search service that isn't in the container yet
const searchService = new Generalsearch_1.GeneralSearchService(DIContainer_1.container.userRepository, DIContainer_1.container.organizationRepository, DIContainer_1.container.deckRepository);
// Auth routes - Get organizations with pagination (RECOMMENDED)
organizationRouter.get('/page/:from/:to', AuthMiddleware_1.authRequired, async (req, res) => {
try {
const from = parseInt(req.params.from);
const to = parseInt(req.params.to);
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)('Get organizations by page endpoint accessed', req, res, { from, to });
const result = await DIContainer_1.container.getOrganizationsByPageQueryHandler.execute({ from, to });
(0, Logger_1.logRequest)('Organizations page retrieved successfully', req, res, {
from,
to,
count: result.organizations.length,
totalCount: result.totalCount
});
res.json(result);
}
catch (error) {
(0, Logger_1.logError)('Get organizations by page endpoint error', error, req, res);
res.status(500).json({ error: 'Internal server error' });
}
});
organizationRouter.get('/search', AuthMiddleware_1.authRequired, async (req, res) => {
try {
const { q: query, limit, offset } = req.query;
(0, Logger_1.logRequest)('Search organizations endpoint accessed', req, res, { query, limit, offset });
if (!query || typeof query !== 'string') {
(0, Logger_1.logWarning)('Organization search attempted without query', { query, hasQuery: !!query }, req, res);
return res.status(400).json({ error: 'Search query is required' });
}
const searchQuery = {
query: query.trim(),
limit: limit ? parseInt(limit) : 20,
offset: offset ? parseInt(offset) : 0
};
// Validate pagination parameters
if (searchQuery.limit < 1 || searchQuery.limit > 100) {
(0, Logger_1.logWarning)('Invalid organization search limit parameter', { limit: searchQuery.limit }, req, res);
return res.status(400).json({ error: 'Limit must be between 1 and 100' });
}
if (searchQuery.offset < 0) {
(0, Logger_1.logWarning)('Invalid organization search offset parameter', { offset: searchQuery.offset }, req, res);
return res.status(400).json({ error: 'Offset must be non-negative' });
}
const result = await searchService.searchFromUrl(req.originalUrl, searchQuery);
(0, Logger_1.logRequest)('Organization search completed successfully', req, res, {
query: searchQuery.query,
resultCount: Array.isArray(result) ? result.length : 0
});
res.json(result);
}
catch (error) {
(0, Logger_1.logError)('Search organizations endpoint error', error, req, res);
res.status(500).json({ error: 'Internal server error' });
}
});
// Get organization login URL
organizationRouter.get('/:orgId/login-url', AuthMiddleware_1.authRequired, async (req, res) => {
try {
const userId = req.user.userId;
const { orgId } = req.params;
(0, Logger_1.logRequest)('Get organization login URL endpoint accessed', req, res, {
userId,
organizationId: orgId
});
const result = await DIContainer_1.container.getOrganizationLoginUrlQueryHandler.execute({
organizationId: orgId
});
if (!result) {
(0, Logger_1.logWarning)('Organization login URL not found', {
organizationId: orgId,
userId
}, req, res);
return ErrorResponseService_1.ErrorResponseService.sendNotFound(res, 'Organization login URL not found');
}
(0, Logger_1.logRequest)('Organization login URL retrieved successfully', req, res, {
organizationId: orgId,
organizationName: result.organizationName,
hasUrl: !!result.loginUrl,
userId
});
res.json(result);
}
catch (error) {
(0, Logger_1.logError)('Get organization login URL endpoint error', error, req, res);
return ErrorResponseService_1.ErrorResponseService.sendInternalServerError(res);
}
});
// Process third-party authentication callback
organizationRouter.post('/auth-callback', AuthMiddleware_1.authRequired, async (req, res) => {
try {
const userId = req.user.userId;
const { organizationId, status, authToken } = req.body;
(0, Logger_1.logRequest)('Organization auth callback endpoint accessed', req, res, {
userId,
organizationId,
status,
hasAuthToken: !!authToken
});
// Validate required fields
if (!organizationId || !status) {
(0, Logger_1.logWarning)('Missing required fields for organization auth callback', {
organizationId: !!organizationId,
status: !!status,
userId
}, req, res);
return ErrorResponseService_1.ErrorResponseService.sendBadRequest(res, 'organizationId and status are required');
}
if (status !== 'ok' && status !== 'not_ok') {
(0, Logger_1.logWarning)('Invalid status value for organization auth callback', {
status,
userId,
organizationId
}, req, res);
return ErrorResponseService_1.ErrorResponseService.sendBadRequest(res, 'status must be either "ok" or "not_ok"');
}
const result = await DIContainer_1.container.processOrgAuthCallbackCommandHandler.execute({
organizationId,
userId,
status,
authToken
});
if (!result.success) {
if (result.message.includes('not found')) {
(0, Logger_1.logWarning)('Organization auth callback failed - entity not found', {
userId,
organizationId,
message: result.message
}, req, res);
return ErrorResponseService_1.ErrorResponseService.sendNotFound(res, result.message);
}
if (result.message.includes('does not belong')) {
(0, Logger_1.logWarning)('Organization auth callback failed - authorization error', {
userId,
organizationId,
message: result.message
}, req, res);
return ErrorResponseService_1.ErrorResponseService.sendForbidden(res, result.message);
}
if (result.message.includes('authentication failed')) {
(0, Logger_1.logAuth)('Organization authentication failed via callback', userId, {
organizationId,
status
}, req, res);
return ErrorResponseService_1.ErrorResponseService.sendUnauthorized(res, result.message);
}
(0, Logger_1.logError)('Organization auth callback internal error', new Error(result.message), req, res);
return ErrorResponseService_1.ErrorResponseService.sendInternalServerError(res);
}
(0, Logger_1.logAuth)('Organization auth callback processed successfully', userId, {
organizationId,
status,
updatedFields: result.updatedFields
}, req, res);
res.json({
success: result.success,
message: result.message,
updatedFields: result.updatedFields
});
}
catch (error) {
(0, Logger_1.logError)('Organization auth callback endpoint error', error, req, res);
return ErrorResponseService_1.ErrorResponseService.sendInternalServerError(res);
}
});
exports.default = organizationRouter;
//# sourceMappingURL=organizationRouter.js.map
File diff suppressed because one or more lines are too long
+3
View File
@@ -0,0 +1,3 @@
declare const userRouter: import("express-serve-static-core").Router;
export default userRouter;
//# sourceMappingURL=userRouter.d.ts.map
@@ -0,0 +1 @@
{"version":3,"file":"userRouter.d.ts","sourceRoot":"","sources":["../../../src/Api/routers/userRouter.ts"],"names":[],"mappings":"AAQA,QAAA,MAAM,UAAU,4CAAW,CAAC;AA+J5B,eAAe,UAAU,CAAC"}
+139
View File
@@ -0,0 +1,139 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const express_1 = require("express");
const AuthMiddleware_1 = require("../../Application/Services/AuthMiddleware");
const DIContainer_1 = require("../../Application/Services/DIContainer");
const ErrorResponseService_1 = require("../../Application/Services/ErrorResponseService");
const ValidationMiddleware_1 = require("../../Application/Services/ValidationMiddleware");
const Generalsearch_1 = require("../../Application/Search/Generalsearch");
const Logger_1 = require("../../Application/Services/Logger");
const userRouter = (0, express_1.Router)();
// Create search service that isn't in the container yet
const searchService = new Generalsearch_1.GeneralSearchService(DIContainer_1.container.userRepository, DIContainer_1.container.organizationRepository, DIContainer_1.container.deckRepository);
// Login endpoint
userRouter.post('/login', ValidationMiddleware_1.ValidationMiddleware.combine([
ValidationMiddleware_1.ValidationMiddleware.validateRequiredFields(['username', 'password']),
ValidationMiddleware_1.ValidationMiddleware.validateStringLength({
username: { min: 3, max: 50 },
password: { min: 6, max: 100 }
})
]), async (req, res) => {
try {
(0, Logger_1.logRequest)('Login endpoint accessed', req, res, { username: req.body.username });
const { username, password } = req.body;
const result = await DIContainer_1.container.loginCommandHandler.execute({ username, password });
if (result) {
(0, Logger_1.logAuth)('User login successful', result.user.id, { username: result.user.username }, req, res);
res.json(result);
}
else {
return ErrorResponseService_1.ErrorResponseService.sendUnauthorized(res, 'Invalid username or password');
}
}
catch (error) {
(0, Logger_1.logError)('Login endpoint error', error, req, res);
if (error instanceof Error) {
if (error.message.includes('Invalid username')) {
return ErrorResponseService_1.ErrorResponseService.sendUnauthorized(res, 'Invalid username or password');
}
if (error.message.includes('Invalid password')) {
return ErrorResponseService_1.ErrorResponseService.sendUnauthorized(res, 'Invalid username or password');
}
if (error.message.includes('not verified')) {
return ErrorResponseService_1.ErrorResponseService.sendUnauthorized(res, 'Please verify your email address');
}
if (error.message.includes('deactivated')) {
return ErrorResponseService_1.ErrorResponseService.sendUnauthorized(res, 'Account has been deactivated');
}
}
return ErrorResponseService_1.ErrorResponseService.sendInternalServerError(res);
}
});
// Create user endpoint
userRouter.post('/create', ValidationMiddleware_1.ValidationMiddleware.combine([
ValidationMiddleware_1.ValidationMiddleware.validateRequiredFields(['username', 'email', 'password']),
ValidationMiddleware_1.ValidationMiddleware.validateEmailFormat(['email']),
ValidationMiddleware_1.ValidationMiddleware.validateStringLength({
username: { min: 3, max: 50 },
password: { min: 6, max: 100 }
})
]), async (req, res) => {
try {
(0, Logger_1.logRequest)('Create user endpoint accessed', req, res, {
username: req.body.username,
email: req.body.email
});
const result = await DIContainer_1.container.createUserCommandHandler.execute(req.body);
(0, Logger_1.logRequest)('User created successfully', req, res, {
userId: result.id,
username: result.username
});
res.status(201).json(result);
}
catch (error) {
(0, Logger_1.logError)('Create user endpoint error', error, req, res);
if (error instanceof Error) {
if (error.message.includes('already exists')) {
return ErrorResponseService_1.ErrorResponseService.sendConflict(res, error.message);
}
if (error.message.includes('validation')) {
return ErrorResponseService_1.ErrorResponseService.sendBadRequest(res, error.message);
}
}
return ErrorResponseService_1.ErrorResponseService.sendInternalServerError(res);
}
});
// Get user profile (current user)
userRouter.get('/profile', AuthMiddleware_1.authRequired, async (req, res) => {
try {
const userId = req.user.userId;
(0, Logger_1.logRequest)('Get user profile endpoint accessed', req, res, { userId });
const result = await DIContainer_1.container.getUserByIdQueryHandler.execute({ id: userId });
if (!result) {
(0, Logger_1.logWarning)('User profile not found', { userId }, req, res);
return ErrorResponseService_1.ErrorResponseService.sendNotFound(res, 'User not found');
}
(0, Logger_1.logRequest)('User profile retrieved successfully', req, res, {
userId,
username: result.username
});
res.json(result);
}
catch (error) {
(0, Logger_1.logError)('Get user profile endpoint error', error, req, res);
return ErrorResponseService_1.ErrorResponseService.sendInternalServerError(res);
}
});
// Update user profile (current user)
userRouter.patch('/profile', AuthMiddleware_1.authRequired, async (req, res) => {
try {
const userId = req.user.userId;
(0, Logger_1.logRequest)('Update user profile endpoint accessed', req, res, {
userId,
fieldsToUpdate: Object.keys(req.body)
});
const result = await DIContainer_1.container.updateUserCommandHandler.execute({ id: userId, ...req.body });
if (!result) {
return ErrorResponseService_1.ErrorResponseService.sendNotFound(res, 'User not found');
}
(0, Logger_1.logRequest)('User profile updated successfully', req, res, {
userId,
username: result.username
});
res.json(result);
}
catch (error) {
(0, Logger_1.logError)('Update user profile endpoint error', error, req, res);
if (error instanceof Error) {
if (error.message.includes('already exists')) {
return ErrorResponseService_1.ErrorResponseService.sendConflict(res, error.message);
}
if (error.message.includes('validation')) {
return ErrorResponseService_1.ErrorResponseService.sendBadRequest(res, error.message);
}
}
return ErrorResponseService_1.ErrorResponseService.sendInternalServerError(res);
}
});
exports.default = userRouter;
//# sourceMappingURL=userRouter.js.map
File diff suppressed because one or more lines are too long