This commit is contained in:
2026-05-13 01:31:13 +05:00
parent 2b6b3fcb06
commit ca3f7fd7c4
22 changed files with 1604 additions and 11 deletions

13
.gitignore vendored
View File

@@ -1,13 +1,4 @@
.env
node_modules
git
promt
/node_modules/
/node_modules*
*.log
package-lock.json
data
data
promt
LICENSE
25.xlsx
.env
data

120
README.md Normal file
View 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
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"
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 144 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 172 KiB

BIN
public/img/geo.webp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 295 KiB

BIN
public/img/hero-bg.webp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 154 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 170 KiB

BIN
public/img/howo-sitrak.webp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 154 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 157 KiB

BIN
public/img/john-deere.webp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 116 KiB

BIN
public/img/kamaz-6520.webp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 130 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 179 KiB

BIN
public/img/ks-45721.webp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 144 KiB

BIN
public/img/license-lom.webp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 514 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 541 KiB

BIN
public/img/logo.webp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 35 KiB

BIN
public/img/secret.webp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 164 KiB

BIN
public/img/tral-1.webp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 112 KiB

BIN
public/img/tral-2.webp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 110 KiB

1329
public/index.html Normal file

File diff suppressed because it is too large Load Diff

141
server.js Normal file
View 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}`);
});