371 lines
12 KiB
TypeScript
371 lines
12 KiB
TypeScript
/**
|
|
* SystemSettingController - Controller de Configuración del Sistema
|
|
*
|
|
* Endpoints REST para gestión de configuraciones.
|
|
*
|
|
* @module Admin
|
|
*/
|
|
|
|
import { Router, Request, Response, NextFunction } from 'express';
|
|
import { DataSource } from 'typeorm';
|
|
import { SystemSettingService, CreateSettingDto, UpdateSettingDto, SettingFilters } from '../services/system-setting.service';
|
|
import { AuthMiddleware } from '../../auth/middleware/auth.middleware';
|
|
import { AuthService } from '../../auth/services/auth.service';
|
|
import { SystemSetting } from '../entities/system-setting.entity';
|
|
import { User } from '../../core/entities/user.entity';
|
|
import { Tenant } from '../../core/entities/tenant.entity';
|
|
import { RefreshToken } from '../../auth/entities/refresh-token.entity';
|
|
import { ServiceContext } from '../../../shared/services/base.service';
|
|
|
|
export function createSystemSettingController(dataSource: DataSource): Router {
|
|
const router = Router();
|
|
|
|
// Repositorios
|
|
const settingRepository = dataSource.getRepository(SystemSetting);
|
|
const userRepository = dataSource.getRepository(User);
|
|
const tenantRepository = dataSource.getRepository(Tenant);
|
|
const refreshTokenRepository = dataSource.getRepository(RefreshToken);
|
|
|
|
// Servicios
|
|
const settingService = new SystemSettingService(settingRepository);
|
|
const authService = new AuthService(userRepository, tenantRepository, refreshTokenRepository as any);
|
|
const authMiddleware = new AuthMiddleware(authService, dataSource);
|
|
|
|
// Helper para crear contexto
|
|
const getContext = (req: Request): ServiceContext => {
|
|
if (!req.tenantId) {
|
|
throw new Error('Tenant ID is required');
|
|
}
|
|
return {
|
|
tenantId: req.tenantId,
|
|
userId: req.user?.sub,
|
|
};
|
|
};
|
|
|
|
/**
|
|
* GET /settings
|
|
*/
|
|
router.get('/', authMiddleware.authenticate, async (req: Request, res: Response, next: NextFunction): Promise<void> => {
|
|
try {
|
|
if (!req.tenantId) {
|
|
res.status(400).json({ error: 'Bad Request', message: 'Tenant ID required' });
|
|
return;
|
|
}
|
|
|
|
const page = parseInt(req.query.page as string) || 1;
|
|
const limit = Math.min(parseInt(req.query.limit as string) || 50, 100);
|
|
|
|
const filters: SettingFilters = {};
|
|
if (req.query.category) filters.category = req.query.category as any;
|
|
if (req.query.isPublic !== undefined) filters.isPublic = req.query.isPublic === 'true';
|
|
if (req.query.search) filters.search = req.query.search as string;
|
|
|
|
const result = await settingService.findWithFilters(getContext(req), filters, page, limit);
|
|
|
|
res.status(200).json({
|
|
success: true,
|
|
data: result.data,
|
|
pagination: result.meta,
|
|
});
|
|
} catch (error) {
|
|
next(error);
|
|
}
|
|
});
|
|
|
|
/**
|
|
* GET /settings/public
|
|
*/
|
|
router.get('/public', authMiddleware.authenticate, async (req: Request, res: Response, next: NextFunction): Promise<void> => {
|
|
try {
|
|
if (!req.tenantId) {
|
|
res.status(400).json({ error: 'Bad Request', message: 'Tenant ID required' });
|
|
return;
|
|
}
|
|
|
|
const settings = await settingService.getPublicSettings(getContext(req));
|
|
res.status(200).json({ success: true, data: settings });
|
|
} catch (error) {
|
|
next(error);
|
|
}
|
|
});
|
|
|
|
/**
|
|
* GET /settings/stats
|
|
*/
|
|
router.get('/stats', authMiddleware.authenticate, authMiddleware.authorize('admin', 'director'), async (req: Request, res: Response, next: NextFunction): Promise<void> => {
|
|
try {
|
|
if (!req.tenantId) {
|
|
res.status(400).json({ error: 'Bad Request', message: 'Tenant ID required' });
|
|
return;
|
|
}
|
|
|
|
const stats = await settingService.getStats(getContext(req));
|
|
res.status(200).json({ success: true, data: stats });
|
|
} catch (error) {
|
|
next(error);
|
|
}
|
|
});
|
|
|
|
/**
|
|
* GET /settings/category/:category
|
|
*/
|
|
router.get('/category/:category', authMiddleware.authenticate, async (req: Request, res: Response, next: NextFunction): Promise<void> => {
|
|
try {
|
|
if (!req.tenantId) {
|
|
res.status(400).json({ error: 'Bad Request', message: 'Tenant ID required' });
|
|
return;
|
|
}
|
|
|
|
const settings = await settingService.findByCategory(getContext(req), req.params.category as any);
|
|
res.status(200).json({ success: true, data: settings });
|
|
} catch (error) {
|
|
next(error);
|
|
}
|
|
});
|
|
|
|
/**
|
|
* GET /settings/key/:key
|
|
*/
|
|
router.get('/key/:key', authMiddleware.authenticate, async (req: Request, res: Response, next: NextFunction): Promise<void> => {
|
|
try {
|
|
if (!req.tenantId) {
|
|
res.status(400).json({ error: 'Bad Request', message: 'Tenant ID required' });
|
|
return;
|
|
}
|
|
|
|
const setting = await settingService.findByKey(getContext(req), req.params.key);
|
|
if (!setting) {
|
|
res.status(404).json({ error: 'Not Found', message: 'Setting not found' });
|
|
return;
|
|
}
|
|
|
|
res.status(200).json({ success: true, data: setting });
|
|
} catch (error) {
|
|
next(error);
|
|
}
|
|
});
|
|
|
|
/**
|
|
* GET /settings/key/:key/value
|
|
*/
|
|
router.get('/key/:key/value', authMiddleware.authenticate, async (req: Request, res: Response, next: NextFunction): Promise<void> => {
|
|
try {
|
|
if (!req.tenantId) {
|
|
res.status(400).json({ error: 'Bad Request', message: 'Tenant ID required' });
|
|
return;
|
|
}
|
|
|
|
const value = await settingService.getValue(getContext(req), req.params.key);
|
|
if (value === null) {
|
|
res.status(404).json({ error: 'Not Found', message: 'Setting not found' });
|
|
return;
|
|
}
|
|
|
|
res.status(200).json({ success: true, data: { key: req.params.key, value } });
|
|
} catch (error) {
|
|
next(error);
|
|
}
|
|
});
|
|
|
|
/**
|
|
* GET /settings/:id
|
|
*/
|
|
router.get('/:id', authMiddleware.authenticate, async (req: Request, res: Response, next: NextFunction): Promise<void> => {
|
|
try {
|
|
if (!req.tenantId) {
|
|
res.status(400).json({ error: 'Bad Request', message: 'Tenant ID required' });
|
|
return;
|
|
}
|
|
|
|
const setting = await settingService.findById(getContext(req), req.params.id);
|
|
if (!setting) {
|
|
res.status(404).json({ error: 'Not Found', message: 'Setting not found' });
|
|
return;
|
|
}
|
|
|
|
res.status(200).json({ success: true, data: setting });
|
|
} catch (error) {
|
|
next(error);
|
|
}
|
|
});
|
|
|
|
/**
|
|
* POST /settings
|
|
*/
|
|
router.post('/', authMiddleware.authenticate, authMiddleware.authorize('admin', 'director'), async (req: Request, res: Response, next: NextFunction): Promise<void> => {
|
|
try {
|
|
if (!req.tenantId) {
|
|
res.status(400).json({ error: 'Bad Request', message: 'Tenant ID required' });
|
|
return;
|
|
}
|
|
|
|
const dto: CreateSettingDto = req.body;
|
|
if (!dto.key || !dto.name || dto.value === undefined) {
|
|
res.status(400).json({
|
|
error: 'Bad Request',
|
|
message: 'key, name, and value are required',
|
|
});
|
|
return;
|
|
}
|
|
|
|
const setting = await settingService.createSetting(getContext(req), dto);
|
|
res.status(201).json({ success: true, data: setting });
|
|
} catch (error) {
|
|
if (error instanceof Error) {
|
|
if (error.message.includes('already exists')) {
|
|
res.status(409).json({ error: 'Conflict', message: error.message });
|
|
return;
|
|
}
|
|
if (error.message.includes('must be') || error.message.includes('pattern')) {
|
|
res.status(400).json({ error: 'Bad Request', message: error.message });
|
|
return;
|
|
}
|
|
}
|
|
next(error);
|
|
}
|
|
});
|
|
|
|
/**
|
|
* PUT /settings/:id
|
|
*/
|
|
router.put('/:id', authMiddleware.authenticate, authMiddleware.authorize('admin', 'director'), async (req: Request, res: Response, next: NextFunction): Promise<void> => {
|
|
try {
|
|
if (!req.tenantId) {
|
|
res.status(400).json({ error: 'Bad Request', message: 'Tenant ID required' });
|
|
return;
|
|
}
|
|
|
|
const dto: UpdateSettingDto = req.body;
|
|
const setting = await settingService.update(getContext(req), req.params.id, dto);
|
|
|
|
if (!setting) {
|
|
res.status(404).json({ error: 'Not Found', message: 'Setting not found' });
|
|
return;
|
|
}
|
|
|
|
res.status(200).json({ success: true, data: setting });
|
|
} catch (error) {
|
|
next(error);
|
|
}
|
|
});
|
|
|
|
/**
|
|
* PUT /settings/key/:key/value
|
|
*/
|
|
router.put('/key/:key/value', authMiddleware.authenticate, authMiddleware.authorize('admin', 'director'), async (req: Request, res: Response, next: NextFunction): Promise<void> => {
|
|
try {
|
|
if (!req.tenantId) {
|
|
res.status(400).json({ error: 'Bad Request', message: 'Tenant ID required' });
|
|
return;
|
|
}
|
|
|
|
const { value } = req.body;
|
|
if (value === undefined) {
|
|
res.status(400).json({ error: 'Bad Request', message: 'value is required' });
|
|
return;
|
|
}
|
|
|
|
const setting = await settingService.updateValue(getContext(req), req.params.key, value);
|
|
if (!setting) {
|
|
res.status(404).json({ error: 'Not Found', message: 'Setting not found' });
|
|
return;
|
|
}
|
|
|
|
res.status(200).json({ success: true, data: setting });
|
|
} catch (error) {
|
|
if (error instanceof Error && (error.message.includes('must be') || error.message.includes('pattern'))) {
|
|
res.status(400).json({ error: 'Bad Request', message: error.message });
|
|
return;
|
|
}
|
|
next(error);
|
|
}
|
|
});
|
|
|
|
/**
|
|
* POST /settings/key/:key/reset
|
|
*/
|
|
router.post('/key/:key/reset', authMiddleware.authenticate, authMiddleware.authorize('admin', 'director'), async (req: Request, res: Response, next: NextFunction): Promise<void> => {
|
|
try {
|
|
if (!req.tenantId) {
|
|
res.status(400).json({ error: 'Bad Request', message: 'Tenant ID required' });
|
|
return;
|
|
}
|
|
|
|
const setting = await settingService.resetToDefault(getContext(req), req.params.key);
|
|
if (!setting) {
|
|
res.status(404).json({ error: 'Not Found', message: 'Setting not found or no default value' });
|
|
return;
|
|
}
|
|
|
|
res.status(200).json({ success: true, data: setting });
|
|
} catch (error) {
|
|
next(error);
|
|
}
|
|
});
|
|
|
|
/**
|
|
* PUT /settings/bulk
|
|
*/
|
|
router.put('/bulk', authMiddleware.authenticate, authMiddleware.authorize('admin', 'director'), async (req: Request, res: Response, next: NextFunction): Promise<void> => {
|
|
try {
|
|
if (!req.tenantId) {
|
|
res.status(400).json({ error: 'Bad Request', message: 'Tenant ID required' });
|
|
return;
|
|
}
|
|
|
|
const { settings } = req.body;
|
|
if (!Array.isArray(settings)) {
|
|
res.status(400).json({ error: 'Bad Request', message: 'settings array is required' });
|
|
return;
|
|
}
|
|
|
|
const result = await settingService.updateMultiple(getContext(req), settings);
|
|
res.status(200).json({
|
|
success: true,
|
|
data: result,
|
|
message: `Updated ${result.updated} settings`,
|
|
});
|
|
} catch (error) {
|
|
next(error);
|
|
}
|
|
});
|
|
|
|
/**
|
|
* DELETE /settings/:id
|
|
*/
|
|
router.delete('/:id', authMiddleware.authenticate, authMiddleware.authorize('admin'), async (req: Request, res: Response, next: NextFunction): Promise<void> => {
|
|
try {
|
|
if (!req.tenantId) {
|
|
res.status(400).json({ error: 'Bad Request', message: 'Tenant ID required' });
|
|
return;
|
|
}
|
|
|
|
// Check if system setting
|
|
const setting = await settingService.findById(getContext(req), req.params.id);
|
|
if (!setting) {
|
|
res.status(404).json({ error: 'Not Found', message: 'Setting not found' });
|
|
return;
|
|
}
|
|
|
|
if (setting.isSystem) {
|
|
res.status(403).json({ error: 'Forbidden', message: 'Cannot delete system settings' });
|
|
return;
|
|
}
|
|
|
|
const deleted = await settingService.hardDelete(getContext(req), req.params.id);
|
|
if (!deleted) {
|
|
res.status(404).json({ error: 'Not Found', message: 'Setting not found' });
|
|
return;
|
|
}
|
|
|
|
res.status(200).json({ success: true, message: 'Setting deleted' });
|
|
} catch (error) {
|
|
next(error);
|
|
}
|
|
});
|
|
|
|
return router;
|
|
}
|
|
|
|
export default createSystemSettingController;
|