v260513
11
.gitignore
vendored
@@ -1,13 +1,4 @@
|
||||
.env
|
||||
node_modules
|
||||
git
|
||||
promt
|
||||
/node_modules/
|
||||
/node_modules*
|
||||
*.log
|
||||
package-lock.json
|
||||
.env
|
||||
data
|
||||
data
|
||||
promt
|
||||
LICENSE
|
||||
25.xlsx
|
||||
120
README.md
Normal file
@@ -0,0 +1,120 @@
|
||||
# СТС-Авто — Демонтаж и капитальный ремонт трубопроводов
|
||||
|
||||
Сайт компании «СТС-Авто». Фронтенд на чистом HTML/CSS/JS, бэкенд на Node.js + Express + SQLite.
|
||||
|
||||
## Запуск
|
||||
|
||||
```bash
|
||||
npm install
|
||||
npm start
|
||||
```
|
||||
|
||||
Откроется на http://localhost:3000
|
||||
|
||||
## Переменные окружения
|
||||
|
||||
Создайте файл `.env`:
|
||||
|
||||
```
|
||||
HOTEL777KEY=ваш_секретный_ключ
|
||||
PORT=3000
|
||||
```
|
||||
|
||||
## REST API
|
||||
|
||||
### Создать заявку
|
||||
|
||||
```
|
||||
POST /api/leads
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"name": "Иван Иванов",
|
||||
"phone": "+7 (999) 123-45-67",
|
||||
"message": "Текст сообщения"
|
||||
}
|
||||
```
|
||||
|
||||
**Ответ:**
|
||||
```json
|
||||
{ "id": 1, "message": "Lead saved" }
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Получить список заявок
|
||||
|
||||
```
|
||||
GET /api/leads
|
||||
X-API-Key: HFwy+tfAljHEq8R21BCRt+Ps4SN65bu8zFagA68N24s
|
||||
```
|
||||
|
||||
**Ответ:**
|
||||
```json
|
||||
[
|
||||
{
|
||||
"id": 1,
|
||||
"name": "Иван Иванов",
|
||||
"phone": "+7 (999) 123-45-67",
|
||||
"message": "Текст сообщения",
|
||||
"created_at": "2026-05-13 15:30:00"
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Удалить заявку
|
||||
|
||||
```
|
||||
DELETE /api/leads/:id
|
||||
X-API-Key: HFwy+tfAljHEq8R21BCRt+Ps4SN65bu8zFagA68N24s
|
||||
```
|
||||
|
||||
**Ответ:**
|
||||
```json
|
||||
{ "message": "Lead deleted" }
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Примеры curl (Windows PowerShell)
|
||||
|
||||
```powershell
|
||||
# Создать заявку
|
||||
$body = @{ name="Иван"; phone="+7 (999) 123-45-67"; message="Тест" } | ConvertTo-Json
|
||||
Invoke-RestMethod -Uri "http://localhost:3000/api/leads" -Method Post -ContentType "application/json" -Body $body
|
||||
|
||||
# Получить заявки
|
||||
$headers = @{ "X-API-Key" = "HFwy+tfAljHEq8R21BCRt+Ps4SN65bu8zFagA68N24s" }
|
||||
Invoke-RestMethod -Uri "http://localhost:3000/api/leads" -Method Get -Headers $headers
|
||||
|
||||
# Удалить заявку
|
||||
Invoke-RestMethod -Uri "http://localhost:3000/api/leads/1" -Method Delete -Headers $headers
|
||||
```
|
||||
|
||||
## Структура проекта
|
||||
|
||||
```
|
||||
sts-avto/
|
||||
├── public/
|
||||
│ ├── index.html # Главная страница
|
||||
│ └── img/ # Изображения (.webp)
|
||||
├── data/
|
||||
│ └── bookings.db # SQLite база данных
|
||||
├── server.js # Express сервер
|
||||
├── .env # Переменные окружения
|
||||
└── package.json
|
||||
```
|
||||
|
||||
## База данных
|
||||
|
||||
Хранится в `data/bookings.db` (SQLite). Таблица `leads`:
|
||||
|
||||
| Поле | Тип | Описание |
|
||||
|------|-----|---------|
|
||||
| id | INTEGER | PK, autoincrement |
|
||||
| name | TEXT | Имя клиента |
|
||||
| phone | TEXT | Телефон |
|
||||
| message | TEXT | Сообщение |
|
||||
| created_at | DATETIME | Дата создания |
|
||||
12
package.json
Normal 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"
|
||||
}
|
||||
}
|
||||
BIN
public/img/belarus-mtz1523.webp
Normal file
|
After Width: | Height: | Size: 144 KiB |
BIN
public/img/caterpillar-336.webp
Normal file
|
After Width: | Height: | Size: 172 KiB |
BIN
public/img/geo.webp
Normal file
|
After Width: | Height: | Size: 295 KiB |
BIN
public/img/hero-bg.webp
Normal file
|
After Width: | Height: | Size: 154 KiB |
BIN
public/img/hitachi-zx350.webp
Normal file
|
After Width: | Height: | Size: 170 KiB |
BIN
public/img/howo-sitrak.webp
Normal file
|
After Width: | Height: | Size: 154 KiB |
BIN
public/img/ivanovets-ks5571.webp
Normal file
|
After Width: | Height: | Size: 157 KiB |
BIN
public/img/john-deere.webp
Normal file
|
After Width: | Height: | Size: 116 KiB |
BIN
public/img/kamaz-6520.webp
Normal file
|
After Width: | Height: | Size: 130 KiB |
BIN
public/img/komatsu-pc400.webp
Normal file
|
After Width: | Height: | Size: 179 KiB |
BIN
public/img/ks-45721.webp
Normal file
|
After Width: | Height: | Size: 144 KiB |
BIN
public/img/license-lom.webp
Normal file
|
After Width: | Height: | Size: 514 KiB |
BIN
public/img/license-otxody.webp
Normal file
|
After Width: | Height: | Size: 541 KiB |
BIN
public/img/logo.webp
Normal file
|
After Width: | Height: | Size: 35 KiB |
BIN
public/img/secret.webp
Normal file
|
After Width: | Height: | Size: 164 KiB |
BIN
public/img/tral-1.webp
Normal file
|
After Width: | Height: | Size: 112 KiB |
BIN
public/img/tral-2.webp
Normal file
|
After Width: | Height: | Size: 110 KiB |
1329
public/index.html
Normal file
141
server.js
Normal file
@@ -0,0 +1,141 @@
|
||||
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);
|
||||
}
|
||||
|
||||
const imgDir = path.join(__dirname, 'public', 'img');
|
||||
const supportedFormats = ['.png', '.jpg', '.jpeg', '.gif', '.bmp', '.tiff'];
|
||||
|
||||
async function convertImagesToWebp() {
|
||||
if (!fs.existsSync(imgDir)) {
|
||||
console.log('📁 Папка img не найдена, пропускаем конвертацию');
|
||||
return;
|
||||
}
|
||||
|
||||
const files = fs.readdirSync(imgDir);
|
||||
const toConvert = files.filter(f => {
|
||||
const ext = path.extname(f).toLowerCase();
|
||||
return supportedFormats.includes(ext) && !f.endsWith('.webp');
|
||||
});
|
||||
|
||||
if (toConvert.length === 0) {
|
||||
console.log('✅ Все изображения уже в формате WebP');
|
||||
return;
|
||||
}
|
||||
|
||||
console.log(`🔄 Конвертация ${toConvert.length} изображений в WebP...`);
|
||||
|
||||
for (const file of toConvert) {
|
||||
const inputPath = path.join(imgDir, file);
|
||||
const outputPath = path.join(imgDir, path.basename(file, path.extname(file)) + '.webp');
|
||||
|
||||
try {
|
||||
await sharp(inputPath)
|
||||
.webp({ quality: 85 })
|
||||
.toFile(outputPath);
|
||||
fs.unlinkSync(inputPath);
|
||||
console.log(` ✅ ${file} → ${path.basename(outputPath)}`);
|
||||
} catch (err) {
|
||||
console.error(` ❌ Ошибка конвертации ${file}:`, err.message);
|
||||
}
|
||||
}
|
||||
|
||||
console.log('✅ Конвертация завершена');
|
||||
}
|
||||
|
||||
// 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 leads (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
name TEXT NOT NULL,
|
||||
phone TEXT NOT NULL,
|
||||
message TEXT,
|
||||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
||||
)`);
|
||||
|
||||
// API: POST /api/leads – сохранить новую заявку (публичный)
|
||||
app.post('/api/leads', (req, res) => {
|
||||
const { name, phone, message } = req.body;
|
||||
if (!name || !phone) {
|
||||
return res.status(400).json({ error: 'Missing required fields: name, phone' });
|
||||
}
|
||||
const stmt = db.prepare(`INSERT INTO leads (name, phone, message) VALUES (?, ?, ?)`);
|
||||
stmt.run(name.trim(), phone.trim(), (message || '').trim(), function(err) {
|
||||
if (err) {
|
||||
console.error(err);
|
||||
return res.status(500).json({ error: 'Database error' });
|
||||
}
|
||||
console.log(`✅ Новая заявка #${this.lastID}: ${name} | ${phone}`);
|
||||
res.status(201).json({ id: this.lastID, message: 'Lead saved' });
|
||||
});
|
||||
stmt.finalize();
|
||||
});
|
||||
|
||||
// API: GET /api/leads – получить список всех заявок (требуется API-ключ)
|
||||
app.get('/api/leads', (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, message, created_at FROM leads ORDER BY created_at DESC`, (err, rows) => {
|
||||
if (err) {
|
||||
console.error(err);
|
||||
return res.status(500).json({ error: 'Database error' });
|
||||
}
|
||||
res.json(rows);
|
||||
});
|
||||
});
|
||||
|
||||
// API: DELETE /api/leads/:id – удалить заявку (требуется API-ключ)
|
||||
app.delete('/api/leads/:id', (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' });
|
||||
}
|
||||
const id = parseInt(req.params.id);
|
||||
if (isNaN(id)) {
|
||||
return res.status(400).json({ error: 'Invalid ID' });
|
||||
}
|
||||
const stmt = db.prepare(`DELETE FROM leads WHERE id = ?`);
|
||||
stmt.run(id, function(err) {
|
||||
if (err) {
|
||||
console.error(err);
|
||||
return res.status(500).json({ error: 'Database error' });
|
||||
}
|
||||
if (this.changes === 0) {
|
||||
return res.status(404).json({ error: 'Lead not found' });
|
||||
}
|
||||
console.log(`🗑️ Удалена заявка #${id}`);
|
||||
res.json({ message: 'Lead deleted' });
|
||||
});
|
||||
stmt.finalize();
|
||||
});
|
||||
|
||||
// Serve frontend
|
||||
app.get('/', (req, res) => {
|
||||
res.sendFile(path.join(__dirname, 'public', 'index.html'));
|
||||
});
|
||||
|
||||
app.listen(PORT, async () => {
|
||||
await convertImagesToWebp();
|
||||
console.log(`✅ server running on http://localhost:${PORT}`);
|
||||
console.log(`🔑 API Key: ${API_KEY}`);
|
||||
});
|
||||