хз
This commit is contained in:
@@ -8,25 +8,26 @@ Hotel 777 — это веб-приложение для бронирования
|
|||||||
- **База данных**: SQLite3
|
- **База данных**: SQLite3
|
||||||
- **Аутентификация**: JWT (jsonwebtoken) + bcryptjs
|
- **Аутентификация**: JWT (jsonwebtoken) + bcryptjs
|
||||||
- **Обработка изображений**: Sharp (конвертация в WebP)
|
- **Обработка изображений**: Sharp (конвертация в WebP)
|
||||||
- **Загрузка файлов**: Multer
|
|
||||||
- **Frontend**: Bootstrap 5, Font Awesome, кастомные шрифты (Inter, Playfair Display)
|
- **Frontend**: Bootstrap 5, Font Awesome, кастомные шрифты (Inter, Playfair Display)
|
||||||
- **Контейнеризация**: Docker + Docker Compose
|
- **Контейнеризация**: Docker + Docker Compose
|
||||||
|
|
||||||
## Структура проекта
|
## Структура проекта
|
||||||
```
|
```
|
||||||
hotell777_260507/
|
hotell777_260507/
|
||||||
├── server.js # Основной файл сервера (784 строки)
|
├── server.js # Основной файл сервера (633 строки)
|
||||||
├── package.json # Зависимости проекта
|
├── package.json # Зависимости проекта
|
||||||
├── Dockerfile # Сборка Docker образа
|
├── Dockerfile # Сборка Docker образа
|
||||||
├── docker-compose.yml # Оркестрация контейнера
|
├── docker-compose.yml # Оркестрация контейнера
|
||||||
├── .env # Переменные окружения
|
├── .env # Переменные окружения (только API_KEY)
|
||||||
├── .gitignore
|
├── .gitignore
|
||||||
|
├── PROJECT_INFO.md # Описание проекта
|
||||||
├── data/
|
├── data/
|
||||||
│ └── bookings.db # База данных SQLite
|
│ └── bookings.db # База данных SQLite
|
||||||
├── public/
|
├── public/
|
||||||
│ ├── index.html # Публичная страница (613 строк)
|
│ ├── index.html # Публичная страница
|
||||||
│ ├── admin.html # Админ-панель (949 строк)
|
│ ├── admin.html # Админ-панель
|
||||||
│ ├── css/ # Стили (Bootstrap, шрифты, кастомные стили)
|
│ ├── css/ # Стили (Bootstrap, шрифты, кастомные стили)
|
||||||
|
│ │ └── files/ # Шрифты Inter и Playfair Display
|
||||||
│ ├── js/
|
│ ├── js/
|
||||||
│ │ ├── main.js # Логика публичной части
|
│ │ ├── main.js # Логика публичной части
|
||||||
│ │ └── bootstrap.bundle.min.js
|
│ │ └── bootstrap.bundle.min.js
|
||||||
@@ -43,8 +44,9 @@ hotell777_260507/
|
|||||||
- Статусы: 'новая', 'оплачена', 'зарезервирована', 'заселена', 'выехала', 'отменена'
|
- Статусы: 'новая', 'оплачена', 'зарезервирована', 'заселена', 'выехала', 'отменена'
|
||||||
|
|
||||||
2. **rooms** — номера отеля
|
2. **rooms** — номера отеля
|
||||||
- Типы: 'Эконом' (2500₽/чел), 'Стандарт' (4000₽/чел), 'VIP Люкс' (8000₽/чел)
|
- Типы: 'Эконом' (2500₽/ночь), 'Стандарт' (4000₽/ночь), 'VIP Люкс' (8000₽/ночь)
|
||||||
- Поля: type, name, description, rooms_count, single_beds, double_beds, has_sofa, has_ac, has_wifi, has_shower, max_guests, price_per_guest, image_path, is_active
|
- Поля: type, name, description, rooms_count, single_beds, double_beds, has_sofa, has_ac, has_wifi, has_shower, max_guests, price_per_guest, image_path, is_active
|
||||||
|
- Номера по умолчанию: 'Эконом 1' (3 шт, 2 односпальные кровати), 'Стандарт 1' (2 шт, 1 двуспальная + диван), 'VIP Люкс 1' (1 шт, 1 двуспальная + диван, до 4 гостей)
|
||||||
|
|
||||||
3. **promocodes** — промокоды
|
3. **promocodes** — промокоды
|
||||||
- Поля: code, discount_percent (1-99%), valid_from, valid_to, valid_days, is_active
|
- Поля: code, discount_percent (1-99%), valid_from, valid_to, valid_days, is_active
|
||||||
@@ -54,18 +56,24 @@ hotell777_260507/
|
|||||||
|
|
||||||
5. **users** — пользователи системы
|
5. **users** — пользователи системы
|
||||||
- Роли: 'admin', 'user'
|
- Роли: 'admin', 'user'
|
||||||
- Автоматическая синхронизация суперадмина из .env
|
- Суперадмин создается при первом запуске (пароль задается через .env)
|
||||||
|
|
||||||
## API Endpoints
|
## API Endpoints
|
||||||
|
|
||||||
### Публичные:
|
### Публичные:
|
||||||
|
- `GET /` — главная страница (index.html)
|
||||||
|
- `GET /admin` — админ-панель (admin.html)
|
||||||
- `GET /api/rooms` — список доступных номеров
|
- `GET /api/rooms` — список доступных номеров
|
||||||
- `POST /api/bookings` — создание бронирования
|
- `POST /api/bookings` — создание бронирования
|
||||||
- `POST /api/promocodes/validate` — проверка промокода и расчет скидки
|
- `POST /api/promocodes/validate` — проверка промокода и расчет скидки
|
||||||
- `GET /api/bookings` — получение всех бронирований (требуется API_KEY в заголовке X-API-Key)
|
- `GET /api/bookings` — получение всех бронирований (требуется API_KEY в заголовке X-API-Key)
|
||||||
|
|
||||||
### Административные (требуется JWT):
|
### Аутентификация:
|
||||||
- `POST /api/admin/login` — вход в систему
|
- `POST /api/auth/login` — вход в систему (login, password)
|
||||||
|
- `PUT /api/auth/me` — обновление профиля (full_name, email)
|
||||||
|
- `POST /api/auth/change-password` — смена пароля (current_password, new_password)
|
||||||
|
|
||||||
|
### Административные (требуется JWT + роль admin):
|
||||||
- `GET /api/admin/bookings` — список всех бронирований
|
- `GET /api/admin/bookings` — список всех бронирований
|
||||||
- `GET /api/admin/bookings/:id/history` — история изменений бронирования
|
- `GET /api/admin/bookings/:id/history` — история изменений бронирования
|
||||||
- `PATCH /api/admin/bookings/:id` — изменение статуса бронирования
|
- `PATCH /api/admin/bookings/:id` — изменение статуса бронирования
|
||||||
@@ -80,10 +88,14 @@ hotell777_260507/
|
|||||||
- `POST /api/admin/promocodes` — создание промокода
|
- `POST /api/admin/promocodes` — создание промокода
|
||||||
- `PUT /api/admin/promocodes/:id` — обновление промокода
|
- `PUT /api/admin/promocodes/:id` — обновление промокода
|
||||||
- `DELETE /api/admin/promocodes/:id` — удаление промокода
|
- `DELETE /api/admin/promocodes/:id` — удаление промокода
|
||||||
|
- `GET /api/admin/users` — список пользователей
|
||||||
|
- `POST /api/admin/users` — создание пользователя
|
||||||
|
- `PUT /api/admin/users/:id` — обновление пользователя
|
||||||
|
- `DELETE /api/admin/users/:id` — удаление пользователя
|
||||||
|
|
||||||
## Основные функции
|
## Основные функции
|
||||||
1. **Публичная часть**:
|
1. **Публичная часть**:
|
||||||
- Просмотр номеров и цен
|
- Просмотр номеров и цен (цена за ночь)
|
||||||
- Бронирование номера с указанием дат, количества гостей
|
- Бронирование номера с указанием дат, количества гостей
|
||||||
- Применение промокодов
|
- Применение промокодов
|
||||||
- Галерея изображений
|
- Галерея изображений
|
||||||
@@ -94,26 +106,28 @@ hotell777_260507/
|
|||||||
- Изменение статусов бронирований
|
- Изменение статусов бронирований
|
||||||
- Управление номерами (создание, редактирование, удаление)
|
- Управление номерами (создание, редактирование, удаление)
|
||||||
- Управление промокодами
|
- Управление промокодами
|
||||||
|
- Управление пользователями (создание, редактирование, удаление)
|
||||||
- История изменений по каждому бронированию
|
- История изменений по каждому бронированию
|
||||||
- Статистика и фильтрация
|
|
||||||
|
|
||||||
3. **Дополнительно**:
|
3. **Аутентификация и пользователи**:
|
||||||
- Автоматическая конвертация изображений в WebP
|
- Вход по логину и паролю (JWT токен на 24 часа)
|
||||||
|
- Смена пароля пользователем
|
||||||
|
- Обновление профиля (имя, email)
|
||||||
|
- Разграничение прав: admin и user
|
||||||
|
|
||||||
|
4. **Дополнительно**:
|
||||||
|
- Автоматическая конвертация изображений в WebP (при наличии в папке img)
|
||||||
- Автоматическое создание номеров по умолчанию
|
- Автоматическое создание номеров по умолчанию
|
||||||
- Проверка доступности промокодов по датам
|
- Проверка доступности промокодов по датам
|
||||||
- Расчет стоимости с учетом скидок
|
- Расчет стоимости с учетом скидок (calculateBasePrice)
|
||||||
|
|
||||||
## Переменные окружения (.env)
|
## Переменные окружения (.env)
|
||||||
```
|
```
|
||||||
PORT=3000
|
|
||||||
HOTEL777KEY=<секретный_ключ_API>
|
HOTEL777KEY=<секретный_ключ_API>
|
||||||
ADMIN_LOGIN=<логин_администратора>
|
|
||||||
ADMIN_PASSWORD=<пароль_администратора>
|
|
||||||
JWT_SECRET=<секрет_JWT>
|
|
||||||
hotelName="Hotel 777"
|
|
||||||
hotelAddress="Абхазия, золотой берег"
|
|
||||||
hotelPhone="+79400000000"
|
|
||||||
```
|
```
|
||||||
|
- PORT=3000 (по умолчанию)
|
||||||
|
- JWT_SECRET (опционально, по умолчанию 'fallback-secret-change-in-production')
|
||||||
|
- Пароль администратора задается при первом запуске через регистрацию
|
||||||
|
|
||||||
## Docker развертывание
|
## Docker развертывание
|
||||||
- Образ: `kalugin66/hotell777_260507`
|
- Образ: `kalugin66/hotell777_260507`
|
||||||
@@ -154,8 +168,10 @@ docker network create applications
|
|||||||
- GitHub: kalugin66
|
- GitHub: kalugin66
|
||||||
|
|
||||||
## Особенности реализации
|
## Особенности реализации
|
||||||
- Все цены указаны в рублях за человека за ночь
|
- Все цены указаны в рублях за номер за ночь
|
||||||
- Количество ночей рассчитывается как разница между датами заезда и выезда
|
- Количество ночей рассчитывается как разница между датами заезда и выезда
|
||||||
- История изменений сохраняется автоматически при любом изменении бронирования
|
- История изменений сохраняется автоматически при любом изменении бронирования
|
||||||
- Суперадмин автоматически создается/обновляется из переменных окружения при запуске
|
- Администраторы создаются через API (`POST /api/admin/users`)
|
||||||
- Изображения номеров загружаются через форму админ-панели и конвертируются в WebP
|
- Автоматическая конвертация изображений в WebP (функция convertImages)
|
||||||
|
- CORS настроен через заголовки (не требуется отдельный middleware)
|
||||||
|
- Multer удален (загрузка изображений через админ-панель отключена)
|
||||||
|
|||||||
18
server.js
18
server.js
@@ -70,6 +70,13 @@ if (!API_KEY) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
app.use(express.json());
|
app.use(express.json());
|
||||||
|
app.use((req, res, next) => {
|
||||||
|
res.header('Access-Control-Allow-Origin', '*');
|
||||||
|
res.header('Access-Control-Allow-Methods', 'GET, POST, PUT, PATCH, DELETE, OPTIONS');
|
||||||
|
res.header('Access-Control-Allow-Headers', 'Content-Type, Authorization');
|
||||||
|
if (req.method === 'OPTIONS') return res.sendStatus(200);
|
||||||
|
next();
|
||||||
|
});
|
||||||
app.use(express.static(path.join(__dirname, 'public')));
|
app.use(express.static(path.join(__dirname, 'public')));
|
||||||
|
|
||||||
const dataDir = path.join(__dirname, 'data');
|
const dataDir = path.join(__dirname, 'data');
|
||||||
@@ -306,6 +313,7 @@ app.get('/api/admin/bookings', authenticateToken, (req, res) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
app.patch('/api/admin/bookings/:id', authenticateToken, requireAdmin, (req, res) => {
|
app.patch('/api/admin/bookings/:id', authenticateToken, requireAdmin, (req, res) => {
|
||||||
|
console.log('PATCH /api/admin/bookings/:id hit', req.params.id, req.body);
|
||||||
const bookingId = parseInt(req.params.id);
|
const bookingId = parseInt(req.params.id);
|
||||||
const { status } = req.body;
|
const { status } = req.body;
|
||||||
const validStatuses = ['новая', 'оплачена', 'зарезервирована', 'заселена', 'выехала', 'отменена'];
|
const validStatuses = ['новая', 'оплачена', 'зарезервирована', 'заселена', 'выехала', 'отменена'];
|
||||||
@@ -319,7 +327,7 @@ app.patch('/api/admin/bookings/:id', authenticateToken, requireAdmin, (req, res)
|
|||||||
db.run(`UPDATE bookings SET status = ? WHERE id = ?`, [status, bookingId], (err) => {
|
db.run(`UPDATE bookings SET status = ? WHERE id = ?`, [status, bookingId], (err) => {
|
||||||
if (err) return res.status(500).json({ error: 'Database error' });
|
if (err) return res.status(500).json({ error: 'Database error' });
|
||||||
logHistory(bookingId, req.user.id, req.user.login, 'status', oldValue, status);
|
logHistory(bookingId, req.user.id, req.user.login, 'status', oldValue, status);
|
||||||
db.get(`SELECT id, name, phone, adults, children, checkin_date, checkout_date, wishes, status, room_type, created_at FROM bookings WHERE id = ?`, [bookingId], (err, row) => {
|
db.get(`SELECT b.*, p.code as promocode_code FROM bookings b LEFT JOIN promocodes p ON b.promocode_id = p.id WHERE b.id = ?`, [bookingId], (err, row) => {
|
||||||
if (err) return res.status(500).json({ error: 'Database error' });
|
if (err) return res.status(500).json({ error: 'Database error' });
|
||||||
res.json({ message: 'Status updated', booking: row });
|
res.json({ message: 'Status updated', booking: row });
|
||||||
});
|
});
|
||||||
@@ -441,6 +449,14 @@ app.post('/api/promocodes/validate', (req, res) => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Public rooms endpoint
|
||||||
|
app.get('/api/rooms', (req, res) => {
|
||||||
|
db.all(`SELECT * FROM rooms WHERE is_active = 1 ORDER BY price_per_guest ASC`, [], (err, rows) => {
|
||||||
|
if (err) { console.error('Rooms API error:', err); return res.status(500).json({ error: 'Database error' }); }
|
||||||
|
res.json(rows);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
app.get('/api/admin/promocodes', authenticateToken, requireAdmin, (req, res) => {
|
app.get('/api/admin/promocodes', authenticateToken, requireAdmin, (req, res) => {
|
||||||
db.all(`SELECT * FROM promocodes ORDER BY created_at DESC`, [], (err, rows) => {
|
db.all(`SELECT * FROM promocodes ORDER BY created_at DESC`, [], (err, rows) => {
|
||||||
if (err) return res.status(500).json({ error: 'Database error' });
|
if (err) return res.status(500).json({ error: 'Database error' });
|
||||||
|
|||||||
12
server.log
Normal file
12
server.log
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
◇ injected env (3) from .env // tip: ⌘ multiple files { path: ['.env.local', '.env'] }
|
||||||
|
✅ HOTEL777KEY is HFwy+tfAljHEq8R21BCRt+Ps4SN65bu8zFagA68N24s
|
||||||
|
✅ Hotel 777 server running on http://localhost:3000
|
||||||
|
✅ Superadmin "admin" updated from .env
|
||||||
|
PATCH /api/admin/bookings/:id hit 2 { status: 'оплачена' }
|
||||||
|
PATCH /api/admin/bookings/:id hit 2 { status: 'заселена' }
|
||||||
|
PATCH /api/admin/bookings/:id hit 2 { status: 'оплачена' }
|
||||||
|
PATCH /api/admin/bookings/:id hit 2 { status: 'новая' }
|
||||||
|
PATCH /api/admin/bookings/:id hit 2 { status: 'оплачена' }
|
||||||
|
PATCH /api/admin/bookings/:id hit 2 { status: 'выехала' }
|
||||||
|
PATCH /api/admin/bookings/:id hit 2 { status: 'оплачена' }
|
||||||
|
PATCH /api/admin/bookings/:id hit 2 { status: 'новая' }
|
||||||
0
server_error.log
Normal file
0
server_error.log
Normal file
Reference in New Issue
Block a user