374 lines
14 KiB
JavaScript
374 lines
14 KiB
JavaScript
const express = require('express');
|
|
const cors = require('cors');
|
|
const path = require('path');
|
|
const fs = require('fs').promises;
|
|
const { connectDB } = require('./config/db');
|
|
const { logger } = require('./config/logger');
|
|
const handbookRoutes = require('./routes/handbook');
|
|
const employeeRoutes = require('./routes/employee');
|
|
const helmet = require('helmet');
|
|
const rateLimit = require('express-rate-limit');
|
|
require('dotenv').config({ path: path.join(__dirname, '../config.env') });
|
|
const limiter = rateLimit({
|
|
windowMs: 15 * 60 * 1000,
|
|
max: 100
|
|
});
|
|
|
|
async function readJson(filePath) {
|
|
const data = await fs.readFile(filePath, 'utf8');
|
|
return JSON.parse(data);
|
|
}
|
|
|
|
async function pathExists(filePath) {
|
|
try {
|
|
await fs.access(filePath);
|
|
return true;
|
|
} catch {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
async function connectToNetworkShare(config) {
|
|
if (config.networkCredentials && config.networkCredentials.username) {
|
|
try {
|
|
const { exec } = require('child_process');
|
|
const { promisify } = require('util');
|
|
const execAsync = promisify(exec);
|
|
const payslipsPath = config.payslipsPath;
|
|
const username = config.networkCredentials.username;
|
|
const password = config.networkCredentials.password;
|
|
const domain = config.networkCredentials.domain;
|
|
const netUseCommand = `net use \\${payslipsPath} ${password} /user:${domain ? domain + '\\' : ''}${username}`;
|
|
|
|
logger.info(`Attempting to connect to network share with credentials`);
|
|
const { stdout, stderr } = await execAsync(netUseCommand);
|
|
|
|
if (stderr) {
|
|
logger.warn(`Network share connection warning: ${stderr}`);
|
|
}
|
|
|
|
logger.info(`Network share connection result: ${stdout}`);
|
|
return true;
|
|
} catch (error) {
|
|
logger.error('Failed to connect to network share', { error: error.message });
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
// ============================================
|
|
// 🚀 Инициализация Express
|
|
// ============================================
|
|
|
|
const app = express();
|
|
app.use(cors());
|
|
app.use(express.json());
|
|
app.use((req, res, next) => {
|
|
if (req.method === 'POST' || req.method === 'PUT') {
|
|
if (!req.is('application/json')) {
|
|
return res.status(415).json({ message: 'Only JSON allowed' });
|
|
}
|
|
}
|
|
next();
|
|
});
|
|
|
|
// ============================================
|
|
// 📍 Маршруты API
|
|
// ============================================
|
|
app.use(helmet());
|
|
app.use('/api/', limiter);
|
|
app.use('/api/handbook', handbookRoutes);
|
|
app.use('/api/employee', employeeRoutes);
|
|
|
|
// ============================================
|
|
// 🧪 Тестовые эндпоинты
|
|
// ============================================
|
|
|
|
app.get('/api/test', (req, res) => {
|
|
res.json({ message: 'API работает' });
|
|
});
|
|
|
|
// ============================================
|
|
// 📊 Эндпоинт для получения статуса системы
|
|
// ============================================
|
|
|
|
app.get('/api/system-status', async (req, res) => {
|
|
try {
|
|
const configPath = path.join(__dirname, '../config.json');
|
|
const config = await readJson(configPath);
|
|
const { getDBStatus } = require('./config/db');
|
|
const dbStatus = await getDBStatus();
|
|
|
|
let networkAccess = false;
|
|
let networkError = null;
|
|
let networkContents = [];
|
|
|
|
try {
|
|
networkAccess = await pathExists(config.payslipsPath);
|
|
if (networkAccess) {
|
|
const stats = await fs.stat(config.payslipsPath);
|
|
networkAccess = stats.isDirectory();
|
|
if (networkAccess) {
|
|
networkContents = await fs.readdir(config.payslipsPath);
|
|
if (networkContents.length > 10) {
|
|
networkContents = networkContents.slice(0, 10);
|
|
networkContents.push(`... и еще ${networkContents.length - 10} элементов`);
|
|
}
|
|
}
|
|
}
|
|
} catch (err) {
|
|
networkAccess = false;
|
|
networkError = err.message;
|
|
}
|
|
|
|
const serverInfo = {
|
|
uptime: process.uptime(),
|
|
memory: process.memoryUsage(),
|
|
nodeVersion: process.version,
|
|
platform: process.platform,
|
|
cwd: process.cwd(),
|
|
pid: process.pid
|
|
};
|
|
|
|
res.json({
|
|
status: 'ok',
|
|
timestamp: new Date().toISOString(),
|
|
server: serverInfo,
|
|
database: dbStatus,
|
|
networkShare: {
|
|
path: config.payslipsPath,
|
|
accessible: networkAccess,
|
|
error: networkError,
|
|
contents: networkContents,
|
|
testMode: config.testMode || false
|
|
},
|
|
config: {
|
|
testMode: config.testMode || false,
|
|
serverHost: config.server?.host || 'localhost',
|
|
serverPort: config.server?.port || '3001'
|
|
}
|
|
});
|
|
|
|
} catch (err) {
|
|
logger.error('Ошибка получения статуса', { error: err.message });
|
|
res.status(500).json({
|
|
status: 'error',
|
|
message: 'Ошибка получения статуса системы',
|
|
error: err.message
|
|
});
|
|
}
|
|
});
|
|
|
|
// ============================================
|
|
// 🌐 Тест сетевой папки
|
|
// ============================================
|
|
|
|
app.get('/api/test-network', async (req, res) => {
|
|
try {
|
|
const configPath = path.join(__dirname, '../config.json');
|
|
const config = await readJson(configPath);
|
|
const payslipBasePath = config.payslipsPath;
|
|
|
|
logger.info(`Testing network connection to: ${payslipBasePath}`);
|
|
|
|
const networkConnected = await connectToNetworkShare(config);
|
|
logger.info(`Network share connection successful: ${networkConnected}`);
|
|
|
|
const testResults = {
|
|
basePath: payslipBasePath,
|
|
basePathExists: false,
|
|
basePathAccessible: false,
|
|
basePathContents: [],
|
|
networkConnected: networkConnected,
|
|
error: null
|
|
};
|
|
|
|
try {
|
|
testResults.basePathExists = await pathExists(payslipBasePath);
|
|
logger.info(`Base path exists: ${testResults.basePathExists}`);
|
|
|
|
if (testResults.basePathExists) {
|
|
const stats = await fs.stat(payslipBasePath);
|
|
testResults.basePathAccessible = stats.isDirectory();
|
|
logger.info(`Base path is directory: ${testResults.basePathAccessible}`);
|
|
|
|
if (testResults.basePathAccessible) {
|
|
testResults.basePathContents = await fs.readdir(payslipBasePath);
|
|
logger.info(`Base path contents: ${JSON.stringify(testResults.basePathContents)}`);
|
|
}
|
|
}
|
|
} catch (error) {
|
|
testResults.error = error.message;
|
|
logger.error('Network test error', { error: error.message, stack: error.stack });
|
|
}
|
|
|
|
res.json(testResults);
|
|
} catch (err) {
|
|
logger.error('Error in network test', { error: err.message, stack: err.stack });
|
|
res.status(500).json({ error: err.message });
|
|
}
|
|
});
|
|
|
|
// ============================================
|
|
// 📄 Получение расчетного листка (с периодом)
|
|
// ============================================
|
|
|
|
app.get('/api/payslip/:period/:fileName', async (req, res) => {
|
|
try {
|
|
const { period, fileName } = req.params;
|
|
const configPath = path.join(__dirname, '../config.json');
|
|
const config = await readJson(configPath);
|
|
const payslipBasePath = config.payslipsPath;
|
|
|
|
logger.info(`Payslip request - Period: ${period}, File: ${fileName}`);
|
|
logger.info(`Base path from config: ${payslipBasePath}`);
|
|
|
|
const networkConnected = await connectToNetworkShare(config);
|
|
logger.info(`Network share connection for payslip: ${networkConnected}`);
|
|
|
|
let finalPayslipPath;
|
|
if (path.isAbsolute(payslipBasePath)) {
|
|
finalPayslipPath = path.join(payslipBasePath, period, fileName);
|
|
} else {
|
|
const projectRoot = path.resolve(__dirname, '../../');
|
|
finalPayslipPath = path.join(projectRoot, payslipBasePath, period, fileName);
|
|
}
|
|
|
|
logger.info(`Final payslip path: ${finalPayslipPath}`);
|
|
|
|
try {
|
|
const basePathExists = await pathExists(payslipBasePath);
|
|
logger.info(`Base path exists: ${basePathExists}`);
|
|
|
|
if (basePathExists) {
|
|
const basePathStats = await fs.stat(payslipBasePath);
|
|
logger.info(`Base path stats: ${JSON.stringify({
|
|
isDirectory: basePathStats.isDirectory(),
|
|
permissions: basePathStats.mode,
|
|
size: basePathStats.size
|
|
})}`);
|
|
|
|
const basePathContents = await fs.readdir(payslipBasePath);
|
|
logger.info(`Base path contents: ${JSON.stringify(basePathContents)}`);
|
|
|
|
const periodPath = path.join(payslipBasePath, period);
|
|
const periodPathExists = await pathExists(periodPath);
|
|
logger.info(`Period path exists: ${periodPathExists} (${periodPath})`);
|
|
|
|
if (periodPathExists) {
|
|
const periodPathContents = await fs.readdir(periodPath);
|
|
logger.info(`Period path contents: ${JSON.stringify(periodPathContents)}`);
|
|
}
|
|
}
|
|
} catch (basePathError) {
|
|
logger.error('Error checking base path', {
|
|
error: basePathError.message,
|
|
stack: basePathError.stack
|
|
});
|
|
}
|
|
|
|
if (await pathExists(finalPayslipPath)) {
|
|
logger.info(`Payslip found, sending file: ${finalPayslipPath}`);
|
|
res.sendFile(finalPayslipPath);
|
|
} else {
|
|
logger.warn(`Payslip not found: ${finalPayslipPath}`);
|
|
res.status(404).json({
|
|
message: 'Расчетный листок не найден',
|
|
path: finalPayslipPath,
|
|
period: period,
|
|
fileName: fileName,
|
|
basePath: payslipBasePath,
|
|
networkConnected: networkConnected
|
|
});
|
|
}
|
|
} catch (err) {
|
|
logger.error('Error getting payslip', { error: err.message, stack: err.stack });
|
|
res.status(500).json({ message: 'Ошибка сервера' });
|
|
}
|
|
});
|
|
|
|
// ============================================
|
|
// 📄 Получение расчетного листка (без периода)
|
|
// ============================================
|
|
|
|
app.get('/api/payslip/:fileName', async (req, res) => {
|
|
try {
|
|
const { fileName } = req.params;
|
|
const configPath = path.join(__dirname, '../config.json');
|
|
const config = await readJson(configPath);
|
|
const payslipBasePath = config.payslipsPath;
|
|
|
|
let finalPayslipPath;
|
|
if (path.isAbsolute(payslipBasePath)) {
|
|
finalPayslipPath = path.join(payslipBasePath, fileName);
|
|
} else {
|
|
const projectRoot = path.resolve(__dirname, '../../');
|
|
finalPayslipPath = path.join(projectRoot, payslipBasePath, fileName);
|
|
}
|
|
|
|
logger.info(`Attempting to find payslip at resolved path: ${finalPayslipPath}`);
|
|
|
|
if (await pathExists(finalPayslipPath)) {
|
|
res.sendFile(finalPayslipPath);
|
|
} else {
|
|
logger.warn(`Payslip not found: ${finalPayslipPath}`);
|
|
res.status(404).json({ message: 'Расчетный листок не найден' });
|
|
}
|
|
} catch (err) {
|
|
logger.error('Error getting payslip', { error: err.message, stack: err.stack });
|
|
res.status(500).json({ message: 'Ошибка сервера' });
|
|
}
|
|
});
|
|
|
|
// ============================================
|
|
|
|
// ============================================
|
|
|
|
app.get('/api/db-status', async (req, res) => {
|
|
try {
|
|
const { query } = require('./config/db');
|
|
await query('SELECT 1');
|
|
res.status(200).json({ status: 'ok', message: 'DB connection successful' });
|
|
} catch (err) {
|
|
logger.error('Database connection check failed', { error: err.message, stack: err.stack });
|
|
res.status(500).json({ status: 'error', message: 'DB connection failed' });
|
|
}
|
|
});
|
|
app.get('/api/time', (req, res) => {
|
|
res.json({
|
|
time: new Date().toISOString(),
|
|
timezone: Intl.DateTimeFormat().resolvedOptions().timeZone
|
|
});
|
|
});
|
|
async function startServer() {
|
|
try {
|
|
const configPath = path.join(__dirname, '../config.json');
|
|
const config = await readJson(configPath);
|
|
const { host, port } = config.server;
|
|
|
|
await connectDB();
|
|
app.get('/api/time', (req, res) => {
|
|
const now = new Date();
|
|
logger.info(`[TIME] Запрос времени от ${req.ip || req.connection.remoteAddress}`);
|
|
res.json({
|
|
time: now.toISOString(),
|
|
timestamp: now.getTime()
|
|
});
|
|
});
|
|
app.listen(port, host, () => {
|
|
logger.info(`Server is running on http://${host}:${port}`);
|
|
});
|
|
} catch (err) {
|
|
if (logger) {
|
|
logger.error('Failed to start server:', { error: err.message, stack: err.stack });
|
|
} else {
|
|
console.error('Failed to start server:', err);
|
|
}
|
|
}
|
|
}
|
|
app.use((err, req, res, next) => {
|
|
logger.error('Unhandled error', { error: err.message, stack: err.stack });
|
|
res.status(500).json({ message: 'Внутренняя ошибка сервера' });
|
|
});
|
|
startServer(); |