This commit is contained in:
2026-05-07 15:23:33 +05:00
commit a26ab569dd
19 changed files with 2115 additions and 0 deletions

4
.gitignore vendored Normal file
View File

@@ -0,0 +1,4 @@
node_modules
package-lock.json
.env
data

12
Dockerfile Normal file
View File

@@ -0,0 +1,12 @@
FROM node:22-alpine
WORKDIR /app
# Устанавливаем git
RUN apk update --no-cache
RUN apk add --no-cache git
RUN git clone https://git.dadehard.ru/kalugin66/hotel777.git .
# Копируем уже склонированный репозиторий с хоста
#COPY ./hotel777 /app
RUN npm i
EXPOSE 3000
CMD ["npm", "start"]

31
docker-compose.yml Normal file
View File

@@ -0,0 +1,31 @@
version: '3.8'
services:
hotell777:
build: .
image: kalugin66/hotell777
container_name: hotell777
# ports:
# - "3000:3000"
networks:
- applications
restart: always
volumes:
- /docker/hotell777/data:/app/data:rw
environment:
- TZ=Asia/Yekaterinburg
- HOTEL777KEY="secretkey"
- hotelName="Hotel 777"
- hotelAddress="Абхазтя золотой берег"
- hotelPhone="+79400000000"
healthcheck:
test: ["CMD", "wget", "--spider", "http://localhost:3000"]
interval: 30s
timeout: 5s
retries: 3
networks:
applications:
external: true
# docker network create applications
# docker compose up -d
# docker compose up -d --build
# docker compose build --no-cache && docker compose up -d

12
package.json Normal file
View File

@@ -0,0 +1,12 @@
{
"dependencies": {
"dotenv": "^17.4.2",
"express": "^5.2.1",
"sharp": "^0.34.5",
"sqlite3": "^6.0.1"
},
"scripts": {
"start": "node server.js",
"dev": "nodemon server.js"
}
}

0
promt Normal file
View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 61 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 127 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 145 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 200 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 144 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 174 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 216 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 115 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 56 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 67 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 53 KiB

BIN
public/img/h777.webp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 289 KiB

1947
public/index.html Normal file

File diff suppressed because it is too large Load Diff

109
server.js Normal file
View File

@@ -0,0 +1,109 @@
const express = require('express');
const path = require('path');
const fs = require('fs');
const sqlite3 = require('sqlite3').verbose();
const sharp = require('sharp');
require('dotenv').config();
const app = express();
const PORT = process.env.PORT || 3000;
const API_KEY = process.env.HOTEL777KEY;
if (!API_KEY) {
console.error('FATAL: HOTEL777KEY environment variable not set');
process.exit(1);
}
// Middleware
app.use(express.json());
app.use(express.static(path.join(__dirname, 'public')));
// Ensure data directory and database
const dataDir = path.join(__dirname, 'data');
if (!fs.existsSync(dataDir)) fs.mkdirSync(dataDir);
const dbPath = path.join(dataDir, 'bookings.db');
const db = new sqlite3.Database(dbPath);
db.run(`CREATE TABLE IF NOT EXISTS bookings (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL,
phone TEXT NOT NULL,
adults INTEGER NOT NULL,
children INTEGER NOT NULL,
checkin_date TEXT NOT NULL,
checkout_date TEXT NOT NULL,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
)`);
// Image conversion (automatically convert JPEG/PNG to WebP)
async function convertImages() {
const imgDir = path.join(__dirname, 'public', 'img');
if (!fs.existsSync(imgDir)) {
console.log('Папка img не найдена, пропускаем конвертацию.');
return;
}
const files = fs.readdirSync(imgDir);
for (const file of files) {
const ext = path.extname(file).toLowerCase();
if (ext === '.jpg' || ext === '.jpeg' || ext === '.png') {
const name = path.parse(file).name;
const webpPath = path.join(imgDir, `${name}.webp`);
if (!fs.existsSync(webpPath)) {
try {
await sharp(path.join(imgDir, file))
.webp({ quality: 85 })
.toFile(webpPath);
console.log(`✅ Сконвертировано: ${file} -> ${name}.webp`);
} catch (err) {
console.error(`❌ Ошибка при конвертации ${file}:`, err);
}
}
}
}
}
// API: POST /api/bookings сохранить новую заявку
app.post('/api/bookings', (req, res) => {
const { name, phone, adults, children, checkin, checkout } = req.body;
if (!name || !phone || !adults || !checkin || !checkout) {
return res.status(400).json({ error: 'Missing required fields' });
}
const stmt = db.prepare(`INSERT INTO bookings (name, phone, adults, children, checkin_date, checkout_date)
VALUES (?, ?, ?, ?, ?, ?)`);
stmt.run(name, phone, adults, children || 0, checkin, checkout, function(err) {
if (err) {
console.error(err);
return res.status(500).json({ error: 'Database error' });
}
res.status(201).json({ id: this.lastID, message: 'Booking saved' });
});
stmt.finalize();
});
// API: GET /api/bookings получить список всех заявок (требуется API-ключ)
app.get('/api/bookings', (req, res) => {
const providedKey = req.headers['x-api-key'];
if (!providedKey || providedKey !== API_KEY) {
return res.status(401).json({ error: 'Invalid or missing API key' });
}
db.all(`SELECT id, name, phone, adults, children, checkin_date, checkout_date, created_at
FROM bookings ORDER BY created_at DESC`, (err, rows) => {
if (err) {
console.error(err);
return res.status(500).json({ error: 'Database error' });
}
res.json(rows);
});
});
// Serve frontend
app.get('/', (req, res) => {
res.sendFile(path.join(__dirname, 'public', 'index.html'));
});
// Start server after image conversion
convertImages().then(() => {
app.listen(PORT, () => {
console.log('✅ HOTEL777KEY is', API_KEY);
console.log(`✅ Hotel 777 server running on http://localhost:${PORT}`);
});
});