337 lines
9.8 KiB
JavaScript
337 lines
9.8 KiB
JavaScript
const express = require('express');
|
||
const router = express.Router();
|
||
|
||
/**
|
||
* Middleware для проверки аутентификации оператора
|
||
*/
|
||
const authenticateOperator = (req, res, next) => {
|
||
const operatorId = req.headers['x-operator-id'];
|
||
|
||
if (!operatorId) {
|
||
return res.status(401).json({ error: 'Operator ID required' });
|
||
}
|
||
|
||
const operator = req.app.locals.deviceManager.getOperator(operatorId);
|
||
if (!operator || !operator.isConnected()) {
|
||
return res.status(401).json({ error: 'Invalid or disconnected operator' });
|
||
}
|
||
|
||
req.operator = operator;
|
||
next();
|
||
};
|
||
|
||
/**
|
||
* Middleware для проверки разрешений
|
||
*/
|
||
const requirePermission = (permission) => {
|
||
return (req, res, next) => {
|
||
if (!req.operator.hasPermission(permission)) {
|
||
return res.status(403).json({ error: `Permission '${permission}' required` });
|
||
}
|
||
next();
|
||
};
|
||
};
|
||
|
||
/**
|
||
* GET /api/operators/devices
|
||
* Получение списка доступных устройств
|
||
*/
|
||
router.get('/devices', authenticateOperator, requirePermission('view_cameras'), (req, res) => {
|
||
try {
|
||
const { deviceManager } = req.app.locals;
|
||
const devices = deviceManager.getAvailableDevicesForOperator(req.operator.operatorId);
|
||
|
||
const devicesData = devices.map(device => device.getSummary());
|
||
|
||
res.json({
|
||
success: true,
|
||
devices: devicesData,
|
||
total: devicesData.length
|
||
});
|
||
} catch (error) {
|
||
res.status(500).json({ error: error.message });
|
||
}
|
||
});
|
||
|
||
/**
|
||
* GET /api/operators/devices/:deviceId
|
||
* Получение информации о конкретном устройстве
|
||
*/
|
||
router.get('/devices/:deviceId', authenticateOperator, requirePermission('view_cameras'), (req, res) => {
|
||
try {
|
||
const { deviceManager } = req.app.locals;
|
||
const device = deviceManager.getDevice(req.params.deviceId);
|
||
|
||
if (!device) {
|
||
return res.status(404).json({ error: 'Device not found' });
|
||
}
|
||
|
||
res.json({
|
||
success: true,
|
||
device: device.getSummary()
|
||
});
|
||
} catch (error) {
|
||
res.status(500).json({ error: error.message });
|
||
}
|
||
});
|
||
|
||
/**
|
||
* POST /api/operators/camera/request
|
||
* Создание запроса на доступ к камере
|
||
*/
|
||
router.post('/camera/request', authenticateOperator, requirePermission('request_camera'), (req, res) => {
|
||
try {
|
||
const { deviceId, cameraType = 'back' } = req.body;
|
||
|
||
if (!deviceId) {
|
||
return res.status(400).json({ error: 'Device ID required' });
|
||
}
|
||
|
||
const { deviceManager, sessionManager, io } = req.app.locals;
|
||
const device = deviceManager.getDevice(deviceId);
|
||
|
||
if (!device) {
|
||
return res.status(404).json({ error: 'Device not found' });
|
||
}
|
||
|
||
if (!device.canAcceptNewSession()) {
|
||
return res.status(409).json({ error: 'Device is busy or unavailable' });
|
||
}
|
||
|
||
// Создаем сессию
|
||
const session = sessionManager.createSession(deviceId, req.operator.operatorId, cameraType);
|
||
|
||
// Отправляем запрос на Android устройство
|
||
device.socket.emit('camera:request', {
|
||
sessionId: session.sessionId,
|
||
operatorId: req.operator.operatorId,
|
||
cameraType
|
||
});
|
||
|
||
// Добавляем сессию к устройству и оператору
|
||
device.addSession(session.sessionId);
|
||
req.operator.addSession(session.sessionId);
|
||
|
||
req.app.locals.logger.info(`Camera request created: ${session.sessionId}`, {
|
||
deviceId,
|
||
operatorId: req.operator.operatorId,
|
||
cameraType
|
||
});
|
||
|
||
res.json({
|
||
success: true,
|
||
sessionId: session.sessionId,
|
||
message: 'Camera request sent to device'
|
||
});
|
||
|
||
} catch (error) {
|
||
req.app.locals.logger.error('Error creating camera request', error);
|
||
res.status(500).json({ error: error.message });
|
||
}
|
||
});
|
||
|
||
/**
|
||
* POST /api/operators/camera/:sessionId/switch
|
||
* Переключение типа камеры в активной сессии
|
||
*/
|
||
router.post('/camera/:sessionId/switch', authenticateOperator, requirePermission('request_camera'), (req, res) => {
|
||
try {
|
||
const { sessionId } = req.params;
|
||
const { cameraType } = req.body;
|
||
|
||
if (!cameraType) {
|
||
return res.status(400).json({ error: 'Camera type required' });
|
||
}
|
||
|
||
const { sessionManager, deviceManager, io } = req.app.locals;
|
||
const session = sessionManager.getSession(sessionId);
|
||
|
||
if (!session) {
|
||
return res.status(404).json({ error: 'Session not found' });
|
||
}
|
||
|
||
if (session.operatorId !== req.operator.operatorId) {
|
||
return res.status(403).json({ error: 'Access denied to this session' });
|
||
}
|
||
|
||
if (session.status !== 'active') {
|
||
return res.status(409).json({ error: 'Session is not active' });
|
||
}
|
||
|
||
const device = deviceManager.getDevice(session.deviceId);
|
||
if (!device || !device.isConnected()) {
|
||
return res.status(409).json({ error: 'Device not available' });
|
||
}
|
||
|
||
// Отправляем команду переключения камеры
|
||
device.socket.emit('camera:switch', {
|
||
sessionId,
|
||
cameraType
|
||
});
|
||
|
||
// Обновляем сессию
|
||
session.switchCamera(cameraType);
|
||
|
||
req.app.locals.logger.info(`Camera switch requested: ${sessionId} -> ${cameraType}`);
|
||
|
||
res.json({
|
||
success: true,
|
||
message: `Camera switch to ${cameraType} requested`
|
||
});
|
||
|
||
} catch (error) {
|
||
req.app.locals.logger.error('Error switching camera', error);
|
||
res.status(500).json({ error: error.message });
|
||
}
|
||
});
|
||
|
||
/**
|
||
* DELETE /api/operators/camera/:sessionId
|
||
* Завершение сессии камеры
|
||
*/
|
||
router.delete('/camera/:sessionId', authenticateOperator, requirePermission('request_camera'), (req, res) => {
|
||
try {
|
||
const { sessionId } = req.params;
|
||
|
||
const { sessionManager, deviceManager } = req.app.locals;
|
||
const session = sessionManager.getSession(sessionId);
|
||
|
||
if (!session) {
|
||
return res.status(404).json({ error: 'Session not found' });
|
||
}
|
||
|
||
if (session.operatorId !== req.operator.operatorId) {
|
||
return res.status(403).json({ error: 'Access denied to this session' });
|
||
}
|
||
|
||
const device = deviceManager.getDevice(session.deviceId);
|
||
|
||
// Отправляем команду отключения (если устройство подключено)
|
||
if (device && device.isConnected()) {
|
||
device.socket.emit('camera:disconnect', { sessionId });
|
||
device.removeSession(sessionId);
|
||
}
|
||
|
||
// Закрываем сессию
|
||
sessionManager.closeSession(sessionId);
|
||
req.operator.removeSession(sessionId);
|
||
|
||
req.app.locals.logger.info(`Session terminated: ${sessionId}`);
|
||
|
||
res.json({
|
||
success: true,
|
||
message: 'Session terminated'
|
||
});
|
||
|
||
} catch (error) {
|
||
req.app.locals.logger.error('Error terminating session', error);
|
||
res.status(500).json({ error: error.message });
|
||
}
|
||
});
|
||
|
||
/**
|
||
* GET /api/operators/sessions
|
||
* Получение активных сессий оператора
|
||
*/
|
||
router.get('/sessions', authenticateOperator, (req, res) => {
|
||
try {
|
||
const { sessionManager } = req.app.locals;
|
||
const sessions = sessionManager.getOperatorSessions(req.operator.operatorId);
|
||
|
||
const sessionsData = sessions.map(session => session.getSummary());
|
||
|
||
res.json({
|
||
success: true,
|
||
sessions: sessionsData,
|
||
total: sessionsData.length
|
||
});
|
||
} catch (error) {
|
||
res.status(500).json({ error: error.message });
|
||
}
|
||
});
|
||
|
||
/**
|
||
* GET /api/operators/sessions/:sessionId
|
||
* Получение детальной информации о сессии
|
||
*/
|
||
router.get('/sessions/:sessionId', authenticateOperator, (req, res) => {
|
||
try {
|
||
const { sessionId } = req.params;
|
||
const { sessionManager } = req.app.locals;
|
||
|
||
const session = sessionManager.getSession(sessionId);
|
||
if (!session) {
|
||
return res.status(404).json({ error: 'Session not found' });
|
||
}
|
||
|
||
if (session.operatorId !== req.operator.operatorId && !req.operator.hasPermission('admin')) {
|
||
return res.status(403).json({ error: 'Access denied to this session' });
|
||
}
|
||
|
||
res.json({
|
||
success: true,
|
||
session: session.getFullInfo()
|
||
});
|
||
} catch (error) {
|
||
res.status(500).json({ error: error.message });
|
||
}
|
||
});
|
||
|
||
/**
|
||
* GET /api/operators/profile
|
||
* Получение профиля текущего оператора
|
||
*/
|
||
router.get('/profile', authenticateOperator, (req, res) => {
|
||
try {
|
||
res.json({
|
||
success: true,
|
||
operator: req.operator.getSummary()
|
||
});
|
||
} catch (error) {
|
||
res.status(500).json({ error: error.message });
|
||
}
|
||
});
|
||
|
||
/**
|
||
* POST /api/operators/ping/:deviceId
|
||
* Проверка доступности устройства
|
||
*/
|
||
router.post('/ping/:deviceId', authenticateOperator, requirePermission('view_cameras'), (req, res) => {
|
||
try {
|
||
const { deviceId } = req.params;
|
||
const { deviceManager } = req.app.locals;
|
||
|
||
const device = deviceManager.getDevice(deviceId);
|
||
if (!device || !device.isConnected()) {
|
||
return res.status(404).json({ error: 'Device not found or disconnected' });
|
||
}
|
||
|
||
const pingStart = Date.now();
|
||
|
||
// Отправляем ping и ждем pong
|
||
device.socket.emit('ping', { timestamp: pingStart }, (response) => {
|
||
const responseTime = Date.now() - pingStart;
|
||
device.updateHealthCheck(responseTime);
|
||
|
||
res.json({
|
||
success: true,
|
||
deviceId,
|
||
responseTime,
|
||
timestamp: new Date().toISOString()
|
||
});
|
||
});
|
||
|
||
// Таймаут для ping
|
||
setTimeout(() => {
|
||
if (!res.headersSent) {
|
||
device.recordError();
|
||
res.status(408).json({ error: 'Device ping timeout' });
|
||
}
|
||
}, 5000);
|
||
|
||
} catch (error) {
|
||
res.status(500).json({ error: error.message });
|
||
}
|
||
});
|
||
|
||
module.exports = router; |