/
h0tnanny
/
IotPlatform
Обзор
Документация
Войти
/
h0tnanny
/
IotPlatform
Код
Запросы
0
Задачи
Вики
Пакеты
0
Релизы
0
CI/CD
Аналитика
Безопасность
develop
src/app.ts
341 строка
12 KB
h0tnanny
Парсинг данных как строчки
20 фев 2026, 19:31
20 фев 2026, 19:31
fbb7684
Код
Авторство
О чём код?
import express, { Express, Request, Response } from 'express'; import * as dotenv from 'dotenv'; import bcrypt from 'bcrypt'; import fs from 'fs'; import path from 'path'; import { Main } from './runtime/Main'; import { createRoutes } from './api/routes'; import { EmulatorService } from './services/EmulatorService'; import { checkDatabaseConnection, closeDatabaseConnection, pool } from './config/database'; import { WorkflowRepository } from './repositories/WorkflowRepository'; import { EquipmentRepository } from './repositories/EquipmentRepository'; import { UserRepository } from './repositories/UserRepository'; import { authConfig } from './config/auth'; import { LogService } from './services/LogService'; // Загружаем переменные окружения dotenv.config(); const app: Express = express(); const PORT = process.env.PORT || 3000; const TICK_RATE = parseInt(process.env.TICK_RATE || '2000', 10); // Middleware // CORS headers для взаимодействия с фронтендом app.use((_req, res, next) => { res.header('Access-Control-Allow-Origin', '*'); res.header('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, PATCH'); res.header('Access-Control-Allow-Headers', 'Content-Type, Authorization'); next(); }); app.use(express.json()); app.use(express.text({ type: 'text/plain' })); // Логирование обращений к API (системные логи в терминал) app.use((req, _res, next) => { console.log(`[API] ${req.method} ${req.path}`); next(); }); // Создаем основной runtime const main = new Main(TICK_RATE); // Создаем сервис эмулятора const emulatorService = new EmulatorService(app, parseInt(String(PORT), 10)); /** * Запускает миграцию auth таблиц */ async function runAuthMigration(): Promise<void> { const schemaPath = path.join(__dirname, '..', 'database', 'auth_schema.sql'); if (!fs.existsSync(schemaPath)) { console.warn('[App] auth_schema.sql не найден, пропускаем миграцию'); return; } try { const schema = fs.readFileSync(schemaPath, 'utf-8'); await pool.query(schema); console.log('[App] Auth миграция выполнена успешно'); } catch (error) { console.error('[App] Ошибка выполнения auth миграции:', error); } } /** * Запускает миграцию таблиц логов */ async function runLogsMigration(): Promise<void> { const schemaPath = path.join(__dirname, '..', 'database', 'logs_schema.sql'); if (!fs.existsSync(schemaPath)) { console.warn('[App] logs_schema.sql не найден, пропускаем миграцию'); return; } try { const schema = fs.readFileSync(schemaPath, 'utf-8'); await pool.query(schema); console.log('[App] Logs миграция выполнена успешно'); } catch (error) { console.error('[App] Ошибка выполнения logs миграции:', error); } } /** * Запускает миграцию таблиц эмулятора */ async function runEmulatorMigration(): Promise<void> { const schemaPath = path.join(__dirname, '..', 'database', 'emulator_schema.sql'); if (!fs.existsSync(schemaPath)) { console.warn('[App] emulator_schema.sql не найден, пропускаем миграцию'); return; } try { const schema = fs.readFileSync(schemaPath, 'utf-8'); await pool.query(schema); console.log('[App] Emulator миграция выполнена успешно'); } catch (error) { console.error('[App] Ошибка выполнения emulator миграции:', error); } } async function runMqttCoapMigration(): Promise<void> { const schemaPath = path.join(__dirname, '..', 'database', 'mqtt_coap_schema.sql'); if (!fs.existsSync(schemaPath)) { console.warn('[App] mqtt_coap_schema.sql не найден, пропускаем миграцию'); return; } try { const schema = fs.readFileSync(schemaPath, 'utf-8'); await pool.query(schema); console.log('[App] MQTT/CoAP миграция выполнена успешно'); } catch (error) { console.error('[App] Ошибка выполнения MQTT/CoAP миграции:', error); } } async function runTriggersMigration(): Promise<void> { const schemaPath = path.join(__dirname, '..', 'database', 'triggers_schema.sql'); if (!fs.existsSync(schemaPath)) { console.warn('[App] triggers_schema.sql не найден, пропускаем миграцию'); return; } try { const schema = fs.readFileSync(schemaPath, 'utf-8'); await pool.query(schema); console.log('[App] Triggers миграция выполнена успешно'); } catch (error) { console.error('[App] Ошибка выполнения triggers миграции:', error); } } async function runPayloadFormatMigration(): Promise<void> { const schemaPath = path.join(__dirname, '..', 'database', 'payload_format_schema.sql'); if (!fs.existsSync(schemaPath)) { console.warn('[App] payload_format_schema.sql не найден, пропускаем миграцию'); return; } try { const schema = fs.readFileSync(schemaPath, 'utf-8'); await pool.query(schema); console.log('[App] Payload format миграция выполнена успешно'); } catch (error) { console.error('[App] Ошибка выполнения payload format миграции:', error); } } async function runEmulatorPayloadSimulationMigration(): Promise<void> { const schemaPath = path.join( __dirname, '..', 'database', 'emulator_payload_simulation.sql' ); if (!fs.existsSync(schemaPath)) { console.warn('[App] emulator_payload_simulation.sql не найден, пропускаем миграцию'); return; } try { const schema = fs.readFileSync(schemaPath, 'utf-8'); await pool.query(schema); console.log('[App] Emulator payload simulation миграция выполнена успешно'); } catch (error) { console.error('[App] Ошибка выполнения emulator payload simulation миграции:', error); } } /** * Seed демо: оборудование с позиционным форматом, эмулятор, workflow */ async function runPositionalDemoSeed(): Promise<void> { const schemaPath = path.join( __dirname, '..', 'database', 'seed_positional_demo.sql' ); if (!fs.existsSync(schemaPath)) { return; } try { const schema = fs.readFileSync(schemaPath, 'utf-8'); await pool.query(schema); console.log('[App] Демо позиционного формата загружено (eq-positional-demo, эмулятор, wf-positional-demo)'); } catch (error) { console.error('[App] Ошибка seed_positional_demo:', error); } } /** * Создает администратора по умолчанию, если его нет */ async function seedDefaultAdmin(): Promise<void> { const userRepo = new UserRepository(); try { const existing = await userRepo.findByUsername(authConfig.defaultAdmin.username); if (existing) { console.log('[App] Администратор по умолчанию уже существует'); return; } const passwordHash = await bcrypt.hash( authConfig.defaultAdmin.password, authConfig.bcryptSaltRounds ); await userRepo.create({ username: authConfig.defaultAdmin.username, email: authConfig.defaultAdmin.email, passwordHash, role: authConfig.defaultAdmin.role, firstName: authConfig.defaultAdmin.firstName, lastName: authConfig.defaultAdmin.lastName, }); console.log(`[App] Администратор создан: ${authConfig.defaultAdmin.username} / ${authConfig.defaultAdmin.password}`); } catch (error) { console.error('[App] Ошибка создания администратора:', error); } } /** * Загружает данные из БД при старте сервера */ async function loadDataFromDatabase(): Promise<void> { console.log('[App] Загрузка данных из БД...'); try { const workflowRepository = new WorkflowRepository(); const equipmentRepository = new EquipmentRepository(); const workflows = await workflowRepository.findAll(); for (const workflow of workflows) { main.addWorkflow(workflow); } const equipmentList = await equipmentRepository.findAll(); for (const equipment of equipmentList) { main.addEquipment(equipment); } console.log(`[App] Инициализация: workflows=${workflows.length}, equipment=${equipmentList.length}`); } catch (error) { console.error('[App] Ошибка загрузки данных из БД:', error); console.log('[App] Сервер продолжит работу с пустым состоянием'); } } /** * Инициализация приложения */ async function initializeApp(): Promise<void> { // Проверяем подключение к БД const dbConnected = await checkDatabaseConnection(); if (dbConnected) { // Запускаем auth миграцию await runAuthMigration(); // Запускаем logs миграцию await runLogsMigration(); // Запускаем emulator миграцию await runEmulatorMigration(); // Запускаем MQTT/CoAP миграцию await runMqttCoapMigration(); // Запускаем triggers миграцию await runTriggersMigration(); await runPayloadFormatMigration(); await runEmulatorPayloadSimulationMigration(); await runPositionalDemoSeed(); // Создаем администратора по умолчанию await seedDefaultAdmin(); // Загружаем данные await loadDataFromDatabase(); // Инициализируем эмулятор (загружаем устройства, запускаем работающие) await emulatorService.initialize(); } else { console.warn('[App] Работа без БД - данные не будут сохраняться'); } // Запускаем runtime loop main.start(); } // API Routes app.use('/', createRoutes(main, emulatorService)); // Health check app.get('/health', (_req: Request, res: Response) => { res.json({ status: 'ok' }); }); // Error handler (критические ошибки — в терминал и в БД) app.use((err: Error, _req: Request, res: Response, _next: express.NextFunction) => { console.error('[API] Ошибка:', err.message); LogService.getInstance().error('system', `Необработанная ошибка: ${err.message}`, { details: { stack: err.stack }, }); res.status(500).json({ error: { code: 'INTERNAL_ERROR', message: err.message || 'Internal server error', }, }); }); // Запуск сервера async function startServer(): Promise<void> { // Инициализируем приложение await initializeApp(); app.listen(PORT, () => { console.log(`[App] Сервер запущен: http://localhost:${PORT}`); console.log('[App] Эндпоинты: /auth/login, /auth/register, /workflows, /equipment, /emulator/devices, /state, /health'); LogService.getInstance().info('system', `Сервер запущен на порту ${PORT}`); }); } // Graceful shutdown async function gracefulShutdown(signal: string): Promise<void> { console.log(`[App] ${signal} — остановка сервера...`); await LogService.getInstance().info('system', `Сервер останавливается (${signal})`); main.stop(); emulatorService.stopAll(); await closeDatabaseConnection(); console.log('[App] Сервер остановлен'); process.exit(0); } process.on('SIGTERM', () => gracefulShutdown('SIGTERM')); process.on('SIGINT', () => gracefulShutdown('SIGINT')); // Запускаем сервер startServer().catch((error) => { console.error('[App] Ошибка запуска сервера:', error); process.exit(1); });