food
This commit is contained in:
@@ -2336,3 +2336,188 @@
|
||||
[2026-08-31T12:27:21.644Z] [INFO] Card logs initialized successfully
|
||||
[2026-08-31T12:30:33.180Z] [INFO] Server running on http://localhost:3005
|
||||
[2026-08-31T12:30:33.183Z] [INFO] Card logs initialized successfully
|
||||
[2026-09-21T06:46:42.261Z] [INFO] Server running on http://localhost:3005
|
||||
[2026-09-21T06:46:42.445Z] [INFO] Card logs initialized successfully
|
||||
[2026-09-24T08:35:23.724Z] [INFO] Server running on http://localhost:3005
|
||||
[2026-09-24T08:35:23.739Z] [INFO] Card logs initialized successfully
|
||||
[2026-10-18T09:42:48.767Z] [INFO] Server running on http://localhost:3005
|
||||
[2026-10-18T09:42:48.774Z] [INFO] Card logs initialized successfully
|
||||
[2026-10-18T17:07:04.649Z] [INFO] Server running on http://localhost:3005
|
||||
[2026-10-18T17:07:04.663Z] [INFO] Card logs initialized successfully
|
||||
[2026-10-18T17:07:45.889Z] [INFO] [DB CHECK] Проверка подключения к MySQL: localhost:3306/rasch_content
|
||||
[2026-10-18T17:07:45.916Z] [INFO] [DB CHECK] ✅ Успешное подключение к MySQL
|
||||
[2026-10-18T17:08:29.318Z] [INFO] [LOGIN] Попытка входа: vova
|
||||
[2026-10-18T17:08:29.320Z] [INFO] [LOGIN] Проверка подключения к базе...
|
||||
[2026-10-18T17:08:29.350Z] [INFO] [LOGIN] Результат поиска пользователя: найден
|
||||
[2026-10-18T17:08:29.352Z] [INFO] [LOGIN] Сравнение пароля через bcrypt...
|
||||
[2026-10-18T17:08:29.458Z] [INFO] [LOGIN] Результат сравнения пароля: true
|
||||
[2026-10-18T17:08:29.461Z] [INFO] [LOGIN] Сессия установлена: {"isModerator":true,"userId":4,"username":"vova"}
|
||||
[2026-10-18T17:08:29.464Z] [INFO] [LOGIN] Ответ отправлен
|
||||
[2026-10-18T17:10:17.359Z] [INFO] [DB CHECK] Проверка подключения к MySQL: localhost:3306/rasch_content
|
||||
[2026-10-18T17:10:17.409Z] [INFO] [DB CHECK] ✅ Успешное подключение к MySQL
|
||||
[2026-10-18T17:10:32.276Z] [WARN] Неверный пароль
|
||||
[2026-10-18T17:10:37.859Z] [WARN] Неверный пароль
|
||||
[2026-10-18T17:10:39.203Z] [WARN] Неверный пароль
|
||||
[2026-10-18T17:15:23.261Z] [INFO] Server running on http://localhost:3005
|
||||
[2026-10-18T17:15:23.275Z] [INFO] Card logs initialized successfully
|
||||
[2026-10-21T03:42:25.554Z] [INFO] Server running on http://localhost:3005
|
||||
[2026-10-21T03:42:25.562Z] [INFO] Card logs initialized successfully
|
||||
[2026-10-21T03:45:23.041Z] [INFO] [LOGIN] Попытка входа: vova
|
||||
[2026-10-21T03:45:23.041Z] [INFO] [LOGIN] Проверка подключения к базе...
|
||||
[2026-10-21T03:45:23.052Z] [INFO] [LOGIN] Результат поиска пользователя: найден
|
||||
[2026-10-21T03:45:23.053Z] [INFO] [LOGIN] Сравнение пароля через bcrypt...
|
||||
[2026-10-21T03:45:23.128Z] [INFO] [LOGIN] Результат сравнения пароля: true
|
||||
[2026-10-21T03:45:23.129Z] [INFO] [LOGIN] Сессия установлена: {"isModerator":true,"userId":4,"username":"vova"}
|
||||
[2026-10-21T03:45:23.141Z] [INFO] [LOGIN] Ответ отправлен
|
||||
[2026-10-21T04:41:10.860Z] [INFO] Server running on http://localhost:3005
|
||||
[2026-10-21T04:41:10.885Z] [INFO] Card logs initialized successfully
|
||||
[2026-10-21T18:14:23.958Z] [INFO] Server running on http://localhost:3005
|
||||
[2026-10-21T18:14:23.964Z] [INFO] Card logs initialized successfully
|
||||
[2026-10-26T10:30:06.261Z] [INFO] Server running on http://localhost:3005
|
||||
[2026-10-26T10:30:06.272Z] [INFO] Card logs initialized successfully
|
||||
[2026-10-28T18:25:51.489Z] [INFO] Server running on http://localhost:3005
|
||||
[2026-10-28T18:25:51.499Z] [INFO] Card logs initialized successfully
|
||||
[2026-10-28T19:29:04.657Z] [INFO] Server running on http://localhost:3005
|
||||
[2026-10-28T19:29:04.725Z] [INFO] Card logs initialized successfully
|
||||
[2026-10-28T20:19:46.381Z] [INFO] Server running on http://localhost:3005
|
||||
[2026-10-28T20:19:46.394Z] [INFO] Card logs initialized successfully
|
||||
[2026-10-28T20:32:55.955Z] [INFO] Server running on http://localhost:3005
|
||||
[2026-10-28T20:32:55.960Z] [INFO] Card logs initialized successfully
|
||||
[2026-10-28T20:46:01.429Z] [INFO] Server running on http://localhost:3005
|
||||
[2026-10-28T20:46:01.433Z] [INFO] Card logs initialized successfully
|
||||
[2026-10-28T20:59:03.413Z] [INFO] [DB CHECK] Проверка подключения к MySQL: localhost:3306/rasch_content
|
||||
[2026-10-28T20:59:03.494Z] [INFO] [DB CHECK] ✅ Успешное подключение к MySQL
|
||||
[2026-10-28T21:00:18.133Z] [INFO] [LOGIN] Попытка входа: vova
|
||||
[2026-10-28T21:00:18.134Z] [INFO] [LOGIN] Проверка подключения к базе...
|
||||
[2026-10-28T21:00:18.154Z] [INFO] [LOGIN] Результат поиска пользователя: найден
|
||||
[2026-10-28T21:00:18.155Z] [INFO] [LOGIN] Сравнение пароля через bcrypt...
|
||||
[2026-10-28T21:00:18.264Z] [INFO] [LOGIN] Результат сравнения пароля: true
|
||||
[2026-10-28T21:00:18.265Z] [INFO] [LOGIN] Сессия установлена: {"isModerator":true,"userId":4,"username":"vova"}
|
||||
[2026-10-28T21:00:18.266Z] [INFO] [LOGIN] Ответ отправлен
|
||||
[2026-10-28T21:13:11.494Z] [INFO] Server running on http://localhost:3005
|
||||
[2026-10-28T21:13:11.515Z] [INFO] Card logs initialized successfully
|
||||
[2026-10-28T21:14:42.274Z] [INFO] [LOGIN] Попытка входа: vova
|
||||
[2026-10-28T21:14:42.274Z] [INFO] [LOGIN] Проверка подключения к базе...
|
||||
[2026-10-28T21:14:42.276Z] [INFO] [LOGIN] Результат поиска пользователя: найден
|
||||
[2026-10-28T21:14:42.277Z] [INFO] [LOGIN] Сравнение пароля через bcrypt...
|
||||
[2026-10-28T21:14:42.390Z] [INFO] [LOGIN] Результат сравнения пароля: true
|
||||
[2026-10-28T21:14:42.391Z] [INFO] [LOGIN] Сессия установлена: {"isModerator":true,"userId":4,"username":"vova"}
|
||||
[2026-10-28T21:14:42.392Z] [INFO] [LOGIN] Ответ отправлен
|
||||
[2026-10-28T21:20:21.465Z] [INFO] Server running on http://localhost:3005
|
||||
[2026-10-28T21:20:21.469Z] [INFO] Card logs initialized successfully
|
||||
[2026-10-28T21:27:08.156Z] [INFO] Server running on http://localhost:3005
|
||||
[2026-10-28T21:27:08.158Z] [INFO] Card logs initialized successfully
|
||||
[2026-10-28T21:27:21.606Z] [ERROR] [admin/food/orders GET] Request failed with status code 500
|
||||
[2026-10-28T21:27:24.092Z] [ERROR] [admin/food/orders GET] Request failed with status code 500
|
||||
[2026-10-28T21:27:25.235Z] [ERROR] [admin/food/orders GET] Request failed with status code 500
|
||||
[2026-10-28T21:27:26.257Z] [ERROR] [admin/food/orders GET] Request failed with status code 500
|
||||
[2026-10-28T21:27:27.402Z] [ERROR] [admin/food/orders GET] Request failed with status code 500
|
||||
[2026-10-28T21:29:56.599Z] [ERROR] [admin/food/orders GET] Request failed with status code 500
|
||||
[2026-10-28T21:29:58.442Z] [ERROR] [admin/food/orders GET] Request failed with status code 500
|
||||
[2026-10-28T21:29:59.302Z] [ERROR] [admin/food/orders GET] Request failed with status code 500
|
||||
[2026-10-28T21:30:01.382Z] [ERROR] [admin/food/orders GET] Request failed with status code 500
|
||||
[2026-10-28T21:30:02.524Z] [ERROR] [admin/food/orders GET] Request failed with status code 500
|
||||
[2026-10-28T21:30:03.627Z] [ERROR] [admin/food/orders GET] Request failed with status code 500
|
||||
[2026-10-28T21:30:16.016Z] [INFO] [LOGIN] Попытка входа: vova
|
||||
[2026-10-28T21:30:16.016Z] [INFO] [LOGIN] Проверка подключения к базе...
|
||||
[2026-10-28T21:30:16.018Z] [INFO] [LOGIN] Результат поиска пользователя: найден
|
||||
[2026-10-28T21:30:16.018Z] [INFO] [LOGIN] Сравнение пароля через bcrypt...
|
||||
[2026-10-28T21:30:16.130Z] [INFO] [LOGIN] Результат сравнения пароля: true
|
||||
[2026-10-28T21:30:16.131Z] [INFO] [LOGIN] Сессия установлена: {"isModerator":true,"userId":4,"username":"vova"}
|
||||
[2026-10-28T21:30:16.132Z] [INFO] [LOGIN] Ответ отправлен
|
||||
[2026-10-28T21:30:19.009Z] [ERROR] [admin/food/orders GET] Request failed with status code 500
|
||||
[2026-10-28T21:30:20.720Z] [ERROR] [admin/food/orders GET] Request failed with status code 500
|
||||
[2026-10-28T21:30:21.657Z] [ERROR] [admin/food/orders GET] Request failed with status code 500
|
||||
[2026-10-28T21:30:22.658Z] [ERROR] [admin/food/orders GET] Request failed with status code 500
|
||||
[2026-10-28T21:30:23.631Z] [ERROR] [admin/food/orders GET] Request failed with status code 500
|
||||
[2026-10-28T21:30:24.526Z] [ERROR] [admin/food/orders GET] Request failed with status code 500
|
||||
[2026-10-28T21:30:24.948Z] [ERROR] [admin/food/orders GET] Request failed with status code 500
|
||||
[2026-10-28T21:35:22.344Z] [INFO] Server running on http://localhost:3005
|
||||
[2026-10-28T21:35:22.348Z] [INFO] Card logs initialized successfully
|
||||
[2026-10-28T21:39:47.954Z] [INFO] Server running on http://localhost:3005
|
||||
[2026-10-28T21:39:47.959Z] [INFO] Card logs initialized successfully
|
||||
[2026-10-28T21:40:26.037Z] [INFO] [LOGIN] Попытка входа: vova
|
||||
[2026-10-28T21:40:26.039Z] [INFO] [LOGIN] Проверка подключения к базе...
|
||||
[2026-10-28T21:40:26.049Z] [INFO] [LOGIN] Результат поиска пользователя: найден
|
||||
[2026-10-28T21:40:26.050Z] [INFO] [LOGIN] Сравнение пароля через bcrypt...
|
||||
[2026-10-28T21:40:26.155Z] [INFO] [LOGIN] Результат сравнения пароля: true
|
||||
[2026-10-28T21:40:26.155Z] [INFO] [LOGIN] Сессия установлена: {"isModerator":true,"userId":4,"username":"vova"}
|
||||
[2026-10-28T21:40:26.157Z] [INFO] [LOGIN] Ответ отправлен
|
||||
[2026-10-28T21:44:16.803Z] [INFO] Server running on http://localhost:3005
|
||||
[2026-10-28T21:44:16.808Z] [INFO] Card logs initialized successfully
|
||||
[2026-10-28T21:44:59.309Z] [INFO] [LOGIN] Попытка входа: vova
|
||||
[2026-10-28T21:44:59.310Z] [INFO] [LOGIN] Проверка подключения к базе...
|
||||
[2026-10-28T21:44:59.312Z] [INFO] [LOGIN] Результат поиска пользователя: найден
|
||||
[2026-10-28T21:44:59.313Z] [INFO] [LOGIN] Сравнение пароля через bcrypt...
|
||||
[2026-10-28T21:44:59.426Z] [INFO] [LOGIN] Результат сравнения пароля: true
|
||||
[2026-10-28T21:44:59.427Z] [INFO] [LOGIN] Сессия установлена: {"isModerator":true,"userId":4,"username":"vova"}
|
||||
[2026-10-28T21:44:59.428Z] [INFO] [LOGIN] Ответ отправлен
|
||||
[2026-10-28T21:48:59.108Z] [INFO] Server running on http://localhost:3005
|
||||
[2026-10-28T21:48:59.112Z] [INFO] Card logs initialized successfully
|
||||
[2026-10-28T21:49:23.402Z] [INFO] [LOGIN] Попытка входа: vova
|
||||
[2026-10-28T21:49:23.403Z] [INFO] [LOGIN] Проверка подключения к базе...
|
||||
[2026-10-28T21:49:23.406Z] [INFO] [LOGIN] Результат поиска пользователя: найден
|
||||
[2026-10-28T21:49:23.407Z] [INFO] [LOGIN] Сравнение пароля через bcrypt...
|
||||
[2026-10-28T21:49:23.520Z] [INFO] [LOGIN] Результат сравнения пароля: true
|
||||
[2026-10-28T21:49:23.522Z] [INFO] [LOGIN] Сессия установлена: {"isModerator":true,"userId":4,"username":"vova"}
|
||||
[2026-10-28T21:49:23.523Z] [INFO] [LOGIN] Ответ отправлен
|
||||
[2026-10-28T21:59:33.857Z] [INFO] Server running on http://localhost:3005
|
||||
[2026-10-28T21:59:33.860Z] [INFO] Card logs initialized successfully
|
||||
[2026-10-28T22:04:29.620Z] [INFO] Server running on http://localhost:3005
|
||||
[2026-10-28T22:04:29.626Z] [INFO] Card logs initialized successfully
|
||||
[2026-10-28T22:12:28.385Z] [INFO] Server running on http://localhost:3005
|
||||
[2026-10-28T22:12:28.411Z] [INFO] Card logs initialized successfully
|
||||
[2026-10-28T22:30:18.979Z] [INFO] Server running on http://localhost:3005
|
||||
[2026-10-28T22:30:18.982Z] [INFO] Card logs initialized successfully
|
||||
[2026-10-28T22:39:36.568Z] [INFO] Server running on http://localhost:3005
|
||||
[2026-10-28T22:39:36.572Z] [INFO] Card logs initialized successfully
|
||||
[2026-10-28T22:43:12.105Z] [INFO] Server running on http://localhost:3005
|
||||
[2026-10-28T22:43:12.109Z] [INFO] Card logs initialized successfully
|
||||
[2026-10-28T22:50:49.508Z] [INFO] Server running on http://localhost:3005
|
||||
[2026-10-28T22:50:49.511Z] [INFO] Card logs initialized successfully
|
||||
[2026-10-28T23:02:07.770Z] [INFO] Server running on http://localhost:3005
|
||||
[2026-10-28T23:02:07.773Z] [INFO] Card logs initialized successfully
|
||||
[2026-10-28T23:02:26.714Z] [INFO] [LOGIN] Попытка входа: vova
|
||||
[2026-10-28T23:02:26.715Z] [INFO] [LOGIN] Проверка подключения к базе...
|
||||
[2026-10-28T23:02:26.728Z] [INFO] [LOGIN] Результат поиска пользователя: найден
|
||||
[2026-10-28T23:02:26.729Z] [INFO] [LOGIN] Сравнение пароля через bcrypt...
|
||||
[2026-10-28T23:02:26.853Z] [INFO] [LOGIN] Результат сравнения пароля: true
|
||||
[2026-10-28T23:02:26.854Z] [INFO] [LOGIN] Сессия установлена: {"isModerator":true,"userId":4,"username":"vova"}
|
||||
[2026-10-28T23:02:26.856Z] [INFO] [LOGIN] Ответ отправлен
|
||||
[2026-10-28T23:03:20.549Z] [ERROR] [admin/food/orders PATCH] Request failed with status code 400
|
||||
[2026-10-28T23:03:24.249Z] [ERROR] [admin/food/orders PATCH] Request failed with status code 400
|
||||
[2026-10-28T23:03:25.985Z] [ERROR] [admin/food/orders PATCH] Request failed with status code 400
|
||||
[2026-10-28T23:03:28.558Z] [ERROR] [admin/food/orders PATCH] Request failed with status code 400
|
||||
[2026-10-28T23:03:31.683Z] [ERROR] [admin/food/orders PATCH] Request failed with status code 400
|
||||
[2026-10-28T23:03:34.003Z] [ERROR] [admin/food/orders PATCH] Request failed with status code 400
|
||||
[2026-10-28T23:03:35.191Z] [ERROR] [admin/food/orders PATCH] Request failed with status code 400
|
||||
[2026-10-28T23:03:36.865Z] [ERROR] [admin/food/orders PATCH] Request failed with status code 400
|
||||
[2026-10-28T23:04:09.307Z] [ERROR] [admin/food/orders PATCH] Request failed with status code 400
|
||||
[2026-10-28T23:04:12.689Z] [ERROR] [admin/food/orders PATCH] Request failed with status code 400
|
||||
[2026-10-28T23:04:14.448Z] [ERROR] [admin/food/orders PATCH] Request failed with status code 400
|
||||
[2026-10-28T23:05:12.507Z] [ERROR] [admin/food/orders PATCH] Request failed with status code 400
|
||||
[2026-10-28T23:07:37.357Z] [INFO] Server running on http://localhost:3005
|
||||
[2026-10-28T23:07:37.367Z] [INFO] Card logs initialized successfully
|
||||
[2026-10-29T01:23:30.890Z] [INFO] Server running on http://localhost:3005
|
||||
[2026-10-29T01:23:30.921Z] [INFO] Card logs initialized successfully
|
||||
[2026-10-29T03:14:32.608Z] [INFO] Server running on http://localhost:3005
|
||||
[2026-10-29T03:14:32.634Z] [INFO] Card logs initialized successfully
|
||||
[2026-10-29T03:15:11.513Z] [INFO] [LOGIN] Попытка входа: vova
|
||||
[2026-10-29T03:15:11.514Z] [INFO] [LOGIN] Проверка подключения к базе...
|
||||
[2026-10-29T03:15:11.537Z] [INFO] [LOGIN] Результат поиска пользователя: найден
|
||||
[2026-10-29T03:15:11.539Z] [INFO] [LOGIN] Сравнение пароля через bcrypt...
|
||||
[2026-10-29T03:15:11.627Z] [INFO] [LOGIN] Результат сравнения пароля: true
|
||||
[2026-10-29T03:15:11.628Z] [INFO] [LOGIN] Сессия установлена: {"isModerator":true,"userId":4,"username":"vova"}
|
||||
[2026-10-29T03:15:11.630Z] [INFO] [LOGIN] Ответ отправлен
|
||||
[2026-10-29T03:15:48.284Z] [WARN] Неверный пароль
|
||||
[2026-10-29T03:15:52.963Z] [WARN] Неверный пароль
|
||||
[2026-10-29T03:15:54.203Z] [WARN] Неверный пароль
|
||||
[2026-10-29T03:16:03.099Z] [WARN] Неверный пароль
|
||||
[2026-10-29T03:16:04.050Z] [WARN] Неверный пароль
|
||||
[2026-10-29T03:16:05.890Z] [WARN] Неверный пароль
|
||||
[2026-10-29T04:24:56.000Z] [INFO] [DB CHECK] Проверка подключения к MySQL: localhost:3306/rasch_content
|
||||
[2026-10-29T04:24:56.088Z] [INFO] [DB CHECK] ✅ Успешное подключение к MySQL
|
||||
[2026-10-29T04:25:34.381Z] [WARN] Неверный пароль
|
||||
[2026-10-29T04:25:38.869Z] [INFO] Login successful
|
||||
[2026-10-29T04:25:39.079Z] [INFO] [DB CHECK] Проверка подключения к MySQL: localhost:3306/rasch_content
|
||||
[2026-10-29T04:25:39.118Z] [INFO] [DB CHECK] ✅ Успешное подключение к MySQL
|
||||
|
||||
@@ -0,0 +1,111 @@
|
||||
const express = require('express');
|
||||
const router = express.Router();
|
||||
const axios = require('axios');
|
||||
const logger = require('../../logger');
|
||||
const multer = require('multer');
|
||||
const FormData = require('form-data');
|
||||
const fs = require('fs');
|
||||
|
||||
const BACKEND_URL = process.env.BACKEND_URL || 'http://localhost:3001';
|
||||
const upload = multer({ dest: 'uploads/tmp/' });
|
||||
|
||||
// ================= ЗАКАЗЫ =================
|
||||
router.get('/orders', async (req, res) => {
|
||||
try {
|
||||
const { status, date } = req.query;
|
||||
const { data } = await axios.get(`${BACKEND_URL}/api/food/orders`, {
|
||||
params: { status, date }, timeout: 5000,
|
||||
});
|
||||
res.json(data);
|
||||
} catch (e) {
|
||||
logger.error('[admin/food/orders GET]', e.message);
|
||||
res.status(500).json({ error: 'Не удалось получить заказы' });
|
||||
}
|
||||
});
|
||||
|
||||
router.patch('/orders/:id/status', async (req, res) => {
|
||||
try {
|
||||
const { data } = await axios.patch(`${BACKEND_URL}/api/food/orders/${req.params.id}/status`, req.body, { timeout: 5000 });
|
||||
res.json(data);
|
||||
} catch (e) {
|
||||
logger.error('[admin/food/orders PATCH]', e.message);
|
||||
res.status(500).json({ error: 'Ошибка обновления статуса' });
|
||||
}
|
||||
});
|
||||
|
||||
// ================= МЕНЮ =================
|
||||
router.get('/items', async (req, res) => {
|
||||
try {
|
||||
const { data } = await axios.get(`${BACKEND_URL}/api/food/menu`);
|
||||
res.json(data);
|
||||
} catch (e) {
|
||||
res.status(500).json({ error: 'Не удалось получить меню' });
|
||||
}
|
||||
});
|
||||
|
||||
router.post('/items', upload.single('image'), async (req, res) => {
|
||||
try {
|
||||
const formData = new FormData();
|
||||
|
||||
// Передаем текстовые поля
|
||||
Object.keys(req.body).forEach(key => {
|
||||
formData.append(key, req.body[key]);
|
||||
});
|
||||
|
||||
// Передаем файл, если он есть
|
||||
if (req.file) {
|
||||
formData.append('image', fs.createReadStream(req.file.path));
|
||||
}
|
||||
|
||||
const { data } = await axios.post(`${BACKEND_URL}/api/food/items`, formData, {
|
||||
headers: formData.getHeaders(),
|
||||
maxContentLength: Infinity,
|
||||
maxBodyLength: Infinity
|
||||
});
|
||||
|
||||
if (req.file) fs.unlinkSync(req.file.path);
|
||||
res.json(data);
|
||||
} catch (e) {
|
||||
console.error('Ошибка проксирования POST:', e.response?.data || e.message);
|
||||
res.status(500).json({ error: e.response?.data?.error || 'Ошибка добавления блюда' });
|
||||
}
|
||||
});
|
||||
|
||||
router.put('/items/:id', upload.single('image'), async (req, res) => {
|
||||
try {
|
||||
const formData = new FormData();
|
||||
|
||||
// Передаем текстовые поля
|
||||
Object.keys(req.body).forEach(key => {
|
||||
formData.append(key, req.body[key]);
|
||||
});
|
||||
|
||||
// Передаем файл, если он есть
|
||||
if (req.file) {
|
||||
formData.append('image', fs.createReadStream(req.file.path));
|
||||
}
|
||||
|
||||
const { data } = await axios.put(`${BACKEND_URL}/api/food/items/${req.params.id}`, formData, {
|
||||
headers: formData.getHeaders(),
|
||||
maxContentLength: Infinity,
|
||||
maxBodyLength: Infinity
|
||||
});
|
||||
|
||||
if (req.file) fs.unlinkSync(req.file.path);
|
||||
res.json(data);
|
||||
} catch (e) {
|
||||
console.error('Ошибка проксирования PUT:', e.response?.data || e.message);
|
||||
res.status(500).json({ error: e.response?.data?.error || 'Ошибка обновления блюда' });
|
||||
}
|
||||
});
|
||||
|
||||
router.delete('/items/:id', async (req, res) => {
|
||||
try {
|
||||
const { data } = await axios.delete(`${BACKEND_URL}/api/food/items/${req.params.id}`);
|
||||
res.json(data);
|
||||
} catch (e) {
|
||||
res.status(500).json({ error: 'Ошибка удаления блюда' });
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = router;
|
||||
Generated
+1
@@ -18,6 +18,7 @@
|
||||
"express-mysql-session": "^3.0.3",
|
||||
"express-rate-limit": "^8.0.1",
|
||||
"express-session": "^1.18.1",
|
||||
"form-data": "^4.0.6",
|
||||
"helmet": "^8.3.0",
|
||||
"morgan": "^1.10.0",
|
||||
"multer": "^2.0.1",
|
||||
|
||||
@@ -22,6 +22,7 @@
|
||||
"express-mysql-session": "^3.0.3",
|
||||
"express-rate-limit": "^8.0.1",
|
||||
"express-session": "^1.18.1",
|
||||
"form-data": "^4.0.6",
|
||||
"helmet": "^8.3.0",
|
||||
"morgan": "^1.10.0",
|
||||
"multer": "^2.0.1",
|
||||
|
||||
+20
-5
@@ -19,6 +19,7 @@ const healthRouter = require('./routes/health');
|
||||
const monitoringRouter = require('./routes/monitoring');
|
||||
const setupWebSocket = require('./services/websocket');
|
||||
const cardController = require('./controllers/cardController');
|
||||
const foodOrdersRouter = require('./moders/routes/foodOrders');
|
||||
const app = express();
|
||||
const server = require('http').createServer(app);
|
||||
|
||||
@@ -27,17 +28,29 @@ const corsConfig = require('./config/cors');
|
||||
const sessionConfig = require('./config/session');
|
||||
|
||||
// Middleware
|
||||
app.use(helmet()); // Добавление helmet для защиты
|
||||
app.use(helmet({
|
||||
contentSecurityPolicy: false, // Разрешаем скрипты на странице питания
|
||||
}));
|
||||
app.use(cors(corsConfig));
|
||||
app.use(morgan('dev'));
|
||||
app.use(express.json({ limit: '100mb' }));
|
||||
app.use(session(sessionConfig(session)));
|
||||
|
||||
// Роуты
|
||||
app.use('/api/auth', authRouter);
|
||||
// Статические файлы
|
||||
app.use(express.static(path.join(__dirname, '..','admin-frontend', 'build')));
|
||||
app.use('/uploads', express.static(__dirname + '/uploads'));
|
||||
|
||||
// ============================================
|
||||
// ПИТАНИЕ (API + HTML страница)
|
||||
// ============================================
|
||||
app.use('/moders/food', foodOrdersRouter); // 👈 ВОТ ЭТА СТРОКА ИСПРАВЛЕНА
|
||||
app.get('/moders/food', (req, res) => {
|
||||
// Отдаем HTML файл
|
||||
res.sendFile(path.join(__dirname, 'views', 'food-orders.html'));
|
||||
});
|
||||
|
||||
// Роуты
|
||||
app.use('/api/card', cardRouter);
|
||||
app.use('/api/logs', logsRouter);
|
||||
@@ -47,10 +60,12 @@ app.use('/api/health', healthRouter);
|
||||
app.use('/api/monitoring', monitoringRouter);
|
||||
app.use('/moders', moderAuthRouter);
|
||||
app.use('/moders/content', moderContentRouter);
|
||||
|
||||
app.use((req, res, next) => {
|
||||
console.log(`[${new Date().toISOString()}] ${req.method} ${req.url}`);
|
||||
next();
|
||||
});
|
||||
|
||||
// Initialize card logs
|
||||
(async () => {
|
||||
try {
|
||||
@@ -74,9 +89,10 @@ app.get(/.*/, (req, res, next) => {
|
||||
|
||||
// Обработчик ошибок (должен быть последним!)
|
||||
app.use(require('./middleware/errorHandler'));
|
||||
|
||||
app.get('/api/status', async (req, res) => {
|
||||
try {
|
||||
res.json({ status: 'online' }); // или другая логика проверки
|
||||
res.json({ status: 'online' });
|
||||
} catch (err) {
|
||||
res.status(500).json({ status: 'error', message: err.message });
|
||||
}
|
||||
@@ -96,19 +112,17 @@ app.get('/api/config', async (req, res) => {
|
||||
res.status(500).json({ error: 'Ошибка загрузки конфигурации' });
|
||||
}
|
||||
});
|
||||
// Добавьте рядом с существующим GET обработчиком
|
||||
|
||||
app.post('/api/config', async (req, res) => {
|
||||
try {
|
||||
const backendConfigPath = path.join(__dirname, '../backend/config.json');
|
||||
const content = await fs.readFile(backendConfigPath, 'utf8');
|
||||
const currentConfig = JSON.parse(content);
|
||||
|
||||
// Обновляем только разрешенные поля
|
||||
if (req.body.payslipsPath) {
|
||||
currentConfig.payslipsPath = req.body.payslipsPath;
|
||||
}
|
||||
|
||||
// Сохраняем обновленный конфиг
|
||||
await fs.writeFile(backendConfigPath, JSON.stringify(currentConfig, null, 4));
|
||||
|
||||
res.json({
|
||||
@@ -123,6 +137,7 @@ app.post('/api/config', async (req, res) => {
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
server.listen(process.env.PORT || 3005, () => {
|
||||
logger.info(`Server running on ${process.env.ADMIN_URL}`);
|
||||
});
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 113 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 113 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 26 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 113 KiB |
@@ -0,0 +1,460 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="ru">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<title>Заказы питания — панель модератора</title>
|
||||
<style>
|
||||
* { box-sizing: border-box; }
|
||||
body {
|
||||
margin: 0;
|
||||
font-family: -apple-system, "Segoe UI", Roboto, sans-serif;
|
||||
background: #f4f7fb;
|
||||
color: #2d3436;
|
||||
}
|
||||
header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 16px 24px;
|
||||
background: #fff;
|
||||
box-shadow: 0 2px 12px rgba(0,0,0,0.06);
|
||||
position: sticky;
|
||||
top: 0;
|
||||
z-index: 10;
|
||||
flex-wrap: wrap;
|
||||
gap: 16px;
|
||||
}
|
||||
header h1 {
|
||||
margin: 0;
|
||||
font-size: 20px;
|
||||
color: #2b6cb0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
}
|
||||
.nav-link {
|
||||
text-decoration: none;
|
||||
color: #2b6cb0;
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
margin-right: 15px;
|
||||
}
|
||||
.nav-link:hover { text-decoration: underline; }
|
||||
.filters {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
.filters button {
|
||||
padding: 8px 16px;
|
||||
border: none;
|
||||
border-radius: 999px;
|
||||
background: #eef2f7;
|
||||
color: #4a5568;
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
.filters button:hover { background: #dde5ef; }
|
||||
.filters button.active {
|
||||
background: #2b6cb0;
|
||||
color: #fff;
|
||||
}
|
||||
.filters button .badge {
|
||||
display: inline-block;
|
||||
margin-left: 6px;
|
||||
background: rgba(255,255,255,0.3);
|
||||
padding: 0 7px;
|
||||
border-radius: 999px;
|
||||
font-size: 12px;
|
||||
}
|
||||
main {
|
||||
max-width: 1100px;
|
||||
margin: 24px auto;
|
||||
padding: 0 16px 60px;
|
||||
}
|
||||
.toolbar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
margin-bottom: 16px;
|
||||
color: #4a5568;
|
||||
font-size: 14px;
|
||||
}
|
||||
.toolbar .dot {
|
||||
width: 10px; height: 10px;
|
||||
border-radius: 50%;
|
||||
background: #2ecc71;
|
||||
animation: pulse 1.5s infinite;
|
||||
}
|
||||
@keyframes pulse {
|
||||
0%, 100% { opacity: 1; }
|
||||
50% { opacity: 0.4; }
|
||||
}
|
||||
.orders {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 14px;
|
||||
}
|
||||
.order {
|
||||
background: #fff;
|
||||
border-radius: 14px;
|
||||
padding: 18px 20px;
|
||||
box-shadow: 0 4px 16px rgba(0,0,0,0.06);
|
||||
border-left: 5px solid #bdc3c7;
|
||||
transition: transform 0.2s;
|
||||
}
|
||||
.order:hover {
|
||||
transform: translateY(-2px);
|
||||
}
|
||||
.order.status-new { border-left-color: #e74c3c; }
|
||||
.order.status-accepted { border-left-color: #f39c12; }
|
||||
.order.status-done { border-left-color: #2ecc71; }
|
||||
.order.status-cancelled { border-left-color: #95a5a6; opacity: 0.7; }
|
||||
|
||||
.order-head {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: flex-start;
|
||||
gap: 12px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
.order-head h2 {
|
||||
margin: 0;
|
||||
font-size: 16px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
.order-id {
|
||||
background: #eef2f7;
|
||||
color: #2b6cb0;
|
||||
padding: 2px 10px;
|
||||
border-radius: 6px;
|
||||
font-size: 14px;
|
||||
font-weight: 700;
|
||||
}
|
||||
.order-time {
|
||||
color: #95a5a6;
|
||||
font-size: 13px;
|
||||
font-weight: 500;
|
||||
}
|
||||
.employee {
|
||||
font-size: 15px;
|
||||
color: #4a5568;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
.employee b { color: #2d3436; }
|
||||
.items {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 6px;
|
||||
padding: 12px 0;
|
||||
border-top: 1px dashed #eef2f7;
|
||||
border-bottom: 1px dashed #eef2f7;
|
||||
margin-bottom: 14px;
|
||||
}
|
||||
.item-row {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
font-size: 14px;
|
||||
color: #2d3436;
|
||||
}
|
||||
.item-row .qty { color: #95a5a6; margin-left: 6px; font-weight: 500; }
|
||||
.order-foot {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
.total {
|
||||
font-size: 18px;
|
||||
font-weight: 700;
|
||||
color: #1abc9c;
|
||||
}
|
||||
.total small { font-size: 13px; color: #95a5a6; font-weight: 500; }
|
||||
.actions {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
.actions button {
|
||||
padding: 8px 18px;
|
||||
border: none;
|
||||
border-radius: 8px;
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
color: #fff;
|
||||
transition: transform 0.1s, opacity 0.2s;
|
||||
}
|
||||
.actions button:active { transform: scale(0.96); }
|
||||
.actions button:hover { opacity: 0.85; }
|
||||
.btn-accept { background: #f39c12; }
|
||||
.btn-done { background: #2ecc71; }
|
||||
.btn-cancel { background: #e74c3c; }
|
||||
.actions button:disabled {
|
||||
background: #bdc3c7;
|
||||
cursor: not-allowed;
|
||||
transform: none;
|
||||
opacity: 0.7;
|
||||
}
|
||||
.empty, .loading {
|
||||
text-align: center;
|
||||
color: #95a5a6;
|
||||
padding: 60px 20px;
|
||||
background: #fff;
|
||||
border-radius: 14px;
|
||||
font-size: 16px;
|
||||
box-shadow: 0 4px 16px rgba(0,0,0,0.04);
|
||||
}
|
||||
.toast {
|
||||
position: fixed;
|
||||
bottom: 30px;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
background: #2d3436;
|
||||
color: #fff;
|
||||
padding: 12px 24px;
|
||||
border-radius: 10px;
|
||||
font-size: 14px;
|
||||
box-shadow: 0 8px 24px rgba(0,0,0,0.25);
|
||||
opacity: 0;
|
||||
transition: opacity 0.3s;
|
||||
pointer-events: none;
|
||||
z-index: 1000;
|
||||
}
|
||||
.toast.show { opacity: 1; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<header>
|
||||
<div style="display: flex; align-items: center;">
|
||||
<a href="/moders" class="nav-link">← В панель модератора</a>
|
||||
<h1>🍔 Заказы питания</h1>
|
||||
</div>
|
||||
<div class="filters" id="filters">
|
||||
<button data-status="new" class="active">Новые <span class="badge" id="b-new">0</span></button>
|
||||
<button data-status="accepted">Принятые <span class="badge" id="b-accepted">0</span></button>
|
||||
<button data-status="done">Выполненные <span class="badge" id="b-done">0</span></button>
|
||||
<button data-status="cancelled">Отменённые <span class="badge" id="b-cancelled">0</span></button>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<main>
|
||||
<div class="toolbar">
|
||||
<div class="dot"></div>
|
||||
<span id="auto-status">Автообновление каждые 10 сек…</span>
|
||||
</div>
|
||||
<div id="container" class="orders">
|
||||
<div class="loading">Загрузка...</div>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<div class="toast" id="toast"></div>
|
||||
|
||||
<script>
|
||||
// ============================================================
|
||||
// Конфиг
|
||||
// ============================================================
|
||||
const API = '/moders/food/orders';
|
||||
const API_STATUS = '/moders/food/orders/';
|
||||
const REFRESH_MS = 10000;
|
||||
|
||||
let currentStatus = 'new';
|
||||
let allOrders = [];
|
||||
|
||||
const container = document.getElementById('container');
|
||||
const toastEl = document.getElementById('toast');
|
||||
|
||||
// ============================================================
|
||||
// Загрузка данных
|
||||
// ============================================================
|
||||
async function loadOrders() {
|
||||
try {
|
||||
const r = await fetch(`${API}?status=${currentStatus}`, {
|
||||
credentials: 'include',
|
||||
});
|
||||
if (!r.ok) throw new Error(`HTTP ${r.status}`);
|
||||
const data = await r.json();
|
||||
allOrders = data;
|
||||
render(data);
|
||||
} catch (e) {
|
||||
console.error('Ошибка загрузки:', e);
|
||||
if (container.innerHTML.includes('loading')) {
|
||||
container.innerHTML = `<div class="empty">Ошибка загрузки: ${e.message}</div>`;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// Бейджи (кол-во по каждому статусу)
|
||||
// ============================================================
|
||||
async function updateBadges() {
|
||||
try {
|
||||
const statuses = ['new', 'accepted', 'done', 'cancelled'];
|
||||
const results = await Promise.all(
|
||||
statuses.map((s) =>
|
||||
fetch(`${API}?status=${s}`, { credentials: 'include' })
|
||||
.then((r) => r.ok ? r.json() : [])
|
||||
.then((arr) => arr.length)
|
||||
.catch(() => 0)
|
||||
)
|
||||
);
|
||||
statuses.forEach((s, i) => {
|
||||
const el = document.getElementById('b-' + s);
|
||||
if (el) el.textContent = results[i];
|
||||
});
|
||||
} catch (err) {
|
||||
// Игнорируем ошибки бейджей
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// Рендер карточек
|
||||
// ============================================================
|
||||
function render(orders) {
|
||||
if (!orders || orders.length === 0) {
|
||||
container.innerHTML = `<div class="empty">Заказов нет</div>`;
|
||||
return;
|
||||
}
|
||||
container.innerHTML = orders.map(renderOrder).join('');
|
||||
|
||||
// Вешаем обработчики на кнопки
|
||||
container.querySelectorAll('[data-action]').forEach((btn) => {
|
||||
btn.addEventListener('click', () => {
|
||||
const id = btn.dataset.id;
|
||||
const status = btn.dataset.action;
|
||||
changeStatus(id, status, btn);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function renderOrder(o) {
|
||||
const itemsHtml = (o.items || [])
|
||||
.map(
|
||||
(it) => `
|
||||
<div class="item-row">
|
||||
<span>${escapeHtml(it.name)}<span class="qty">× ${it.quantity}</span></span>
|
||||
<span>${Number(it.subtotal).toFixed(2)} BYN</span>
|
||||
</div>`
|
||||
)
|
||||
.join('');
|
||||
|
||||
const actions = [];
|
||||
|
||||
if (o.status === 'new') {
|
||||
actions.push(`<button class="btn-accept" data-action="accepted" data-id="${o.id}">Принять</button>`);
|
||||
actions.push(`<button class="btn-cancel" data-action="cancelled" data-id="${o.id}">Отменить</button>`);
|
||||
} else if (o.status === 'accepted') {
|
||||
actions.push(`<button class="btn-done" data-action="done" data-id="${o.id}">Выполнен</button>`);
|
||||
actions.push(`<button class="btn-cancel" data-action="cancelled" data-id="${o.id}">Отменить</button>`);
|
||||
} else if (o.status === 'cancelled') {
|
||||
actions.push(`<button class="btn-accept" data-action="accepted" data-id="${o.id}">Восстановить</button>`);
|
||||
}
|
||||
|
||||
return `
|
||||
<div class="order status-${o.status}">
|
||||
<div class="order-head">
|
||||
<h2><span class="order-id">Заказ #${o.id}</span></h2>
|
||||
<span class="order-time">${formatDate(o.createdAt)}</span>
|
||||
</div>
|
||||
<div class="employee">
|
||||
${escapeHtml(o.employee || 'Сотрудник')} · таб. № <b>${escapeHtml(o.tnom)}</b>
|
||||
</div>
|
||||
<div class="items">${itemsHtml}</div>
|
||||
<div class="order-foot">
|
||||
<div class="total">${Number(o.totalAmount).toFixed(2)} <small>BYN</small></div>
|
||||
<div class="actions">${actions.join('')}</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// Смена статуса
|
||||
// ============================================================
|
||||
async function changeStatus(id, status, btn) {
|
||||
if (btn) {
|
||||
btn.disabled = true;
|
||||
btn.textContent = '...';
|
||||
}
|
||||
try {
|
||||
const r = await fetch(`${API_STATUS}${id}/status`, {
|
||||
method: 'PATCH',
|
||||
credentials: 'include',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ status }),
|
||||
});
|
||||
if (!r.ok) throw new Error(`HTTP ${r.status}`);
|
||||
|
||||
showToast(`Заказ #${id} → ${statusLabel(status)}`);
|
||||
await loadOrders();
|
||||
await updateBadges();
|
||||
} catch (e) {
|
||||
showToast(`Ошибка: ${e.message}`);
|
||||
if (btn) {
|
||||
btn.disabled = false;
|
||||
// Кнопка вернет свой текст при перерисовке
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function statusLabel(s) {
|
||||
return { new: 'Новый', accepted: 'Принят', done: 'Выполнен', cancelled: 'Отменён' }[s] || s;
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// Утилиты
|
||||
// ============================================================
|
||||
function escapeHtml(str) {
|
||||
return String(str || '').replace(/[&<>"']/g, (c) => ({
|
||||
'&': '&', '<': '<', '>': '>', '"': '"', "'": '''
|
||||
}[c]));
|
||||
}
|
||||
|
||||
function formatDate(iso) {
|
||||
const d = new Date(iso);
|
||||
return d.toLocaleString('ru-RU', {
|
||||
day: '2-digit', month: '2-digit', year: 'numeric',
|
||||
hour: '2-digit', minute: '2-digit',
|
||||
});
|
||||
}
|
||||
|
||||
let toastTimer = null;
|
||||
function showToast(text) {
|
||||
toastEl.textContent = text;
|
||||
toastEl.classList.add('show');
|
||||
clearTimeout(toastTimer);
|
||||
toastTimer = setTimeout(() => toastEl.classList.remove('show'), 2500);
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// Фильтры
|
||||
// ============================================================
|
||||
document.getElementById('filters').addEventListener('click', (e) => {
|
||||
const btn = e.target.closest('button[data-status]');
|
||||
if (!btn) return;
|
||||
document.querySelectorAll('#filters button').forEach((b) => b.classList.remove('active'));
|
||||
btn.classList.add('active');
|
||||
currentStatus = btn.dataset.status;
|
||||
container.innerHTML = `<div class="loading">Загрузка...</div>`;
|
||||
loadOrders();
|
||||
});
|
||||
|
||||
// ============================================================
|
||||
// Старт и Автообновление
|
||||
// ============================================================
|
||||
loadOrders();
|
||||
updateBadges();
|
||||
|
||||
setInterval(() => {
|
||||
loadOrders();
|
||||
updateBadges();
|
||||
}, REFRESH_MS);
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user