197 lines
7.0 KiB
TypeScript
197 lines
7.0 KiB
TypeScript
import { Router } from 'express';
|
|
import { authRequired } from '../../Application/Services/AuthMiddleware';
|
|
import { container } from '../../Application/Services/DIContainer';
|
|
import { ErrorResponseService } from '../../Application/Services/ErrorResponseService';
|
|
import { ValidationMiddleware } from '../../Application/Services/ValidationMiddleware';
|
|
import { GeneralSearchService } from '../../Application/Search/Generalsearch';
|
|
import { logRequest, logError, logWarning } from '../../Application/Services/Logger';
|
|
|
|
const deckRouter = Router();
|
|
|
|
// Create search service that isn't in the container yet
|
|
const searchService = new GeneralSearchService(container.userRepository, container.organizationRepository, container.deckRepository);
|
|
|
|
// Authenticated routes - Get decks with pagination (RECOMMENDED)
|
|
deckRouter.get('/page/:from/:to', authRequired, async (req, res) => {
|
|
try {
|
|
const userId = (req as any).user.userId;
|
|
const userOrgId = (req as any).user.orgId;
|
|
const isAdmin = (req as any).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' });
|
|
}
|
|
|
|
logRequest('Get decks by page endpoint accessed', req, res, {
|
|
userId,
|
|
userOrgId,
|
|
isAdmin,
|
|
from,
|
|
to
|
|
});
|
|
|
|
// Use paginated query handler for memory efficiency
|
|
const result = await container.getDecksByPageQueryHandler.execute({
|
|
userId,
|
|
userOrgId,
|
|
isAdmin,
|
|
from,
|
|
to
|
|
});
|
|
|
|
logRequest('Get decks page completed successfully', req, res, {
|
|
userId,
|
|
from,
|
|
to,
|
|
returnedCount: result.decks.length,
|
|
totalCount: result.totalCount
|
|
});
|
|
|
|
res.json(result);
|
|
} catch (error) {
|
|
logError('Get decks by page endpoint error', error as Error, req, res);
|
|
res.status(500).json({ error: 'Internal server error' });
|
|
}
|
|
});
|
|
|
|
deckRouter.post('/', authRequired, async (req, res) => {
|
|
try {
|
|
const userId = (req as any).user.userId;
|
|
logRequest('Create deck endpoint accessed', req, res, { name: req.body.name, userId });
|
|
req.body.userid = userId; // Set userId in request body
|
|
const result = await container.createDeckCommandHandler.execute(req.body);
|
|
|
|
logRequest('Deck created successfully', req, res, { deckId: result.id, name: req.body.name, userId });
|
|
res.json(result);
|
|
} catch (error) {
|
|
logError('Create deck endpoint error', error as 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', authRequired, async (req, res) => {
|
|
try {
|
|
const { query, limit, offset } = req.query;
|
|
logRequest('Search decks endpoint accessed', req, res, { query, limit, offset });
|
|
|
|
if (!query || typeof query !== 'string') {
|
|
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 as string) : 20,
|
|
offset: offset ? parseInt(offset as string) : 0
|
|
};
|
|
|
|
// Validate pagination parameters
|
|
if (searchQuery.limit < 1 || searchQuery.limit > 100) {
|
|
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) {
|
|
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);
|
|
|
|
logRequest('Deck search completed successfully', req, res, {
|
|
query: searchQuery.query,
|
|
resultCount: Array.isArray(result) ? result.length : 0
|
|
});
|
|
res.json(result);
|
|
} catch (error) {
|
|
logError('Search decks endpoint error', error as Error, req, res);
|
|
res.status(500).json({ error: 'Internal server error' });
|
|
}
|
|
});
|
|
|
|
deckRouter.get('/:id', authRequired, async (req, res) => {
|
|
try {
|
|
const deckId = req.params.id;
|
|
logRequest('Get deck by id endpoint accessed', req, res, { deckId });
|
|
|
|
const result = await container.getDeckByIdQueryHandler.execute({ id: deckId });
|
|
|
|
if (!result) {
|
|
logWarning('Deck not found', { deckId }, req, res);
|
|
return res.status(404).json({ error: 'Deck not found' });
|
|
}
|
|
|
|
logRequest('Deck retrieved successfully', req, res, { deckId });
|
|
res.json(result);
|
|
} catch (error) {
|
|
logError('Get deck by id endpoint error', error as Error, req, res);
|
|
res.status(500).json({ error: 'Internal server error' });
|
|
}
|
|
});
|
|
|
|
deckRouter.patch('/:id', authRequired, async (req, res) => {
|
|
try {
|
|
const deckId = req.params.id;
|
|
const userId = (req as any).user.userId;
|
|
logRequest('Update deck endpoint accessed', req, res, { deckId, userId, updateFields: Object.keys(req.body) });
|
|
|
|
const result = await container.updateDeckCommandHandler.execute({ id: deckId, ...req.body });
|
|
|
|
logRequest('Deck updated successfully', req, res, { deckId, userId });
|
|
res.json(result);
|
|
} catch (error) {
|
|
logError('Update deck endpoint error', error as 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 });
|
|
}
|
|
|
|
if (error instanceof Error && error.message.includes('admin')) {
|
|
return res.status(403).json({ error: 'Forbidden: ' + error.message });
|
|
}
|
|
|
|
res.status(500).json({ error: 'Internal server error' });
|
|
}
|
|
});
|
|
|
|
deckRouter.delete('/:id', authRequired, async (req, res) => {
|
|
try {
|
|
const deckId = req.params.id;
|
|
const userId = (req as any).user.userId;
|
|
logRequest('Soft delete deck endpoint accessed', req, res, { deckId, userId });
|
|
|
|
const result = await container.deleteDeckCommandHandler.execute({ id: deckId, soft: true });
|
|
|
|
logRequest('Deck soft delete successful', req, res, { deckId, userId, success: result });
|
|
res.json({ success: result });
|
|
} catch (error) {
|
|
logError('Soft delete deck endpoint error', error as 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' });
|
|
}
|
|
});
|
|
|
|
export default deckRouter; |