Files
chedius dee912edb8
Build and Push Docker Images / build-backend (push) Canceled after 0s
Build and Push Docker Images / build-frontend (push) Canceled after 0s
Improve delivery workflows and add versioned guided tours (#1)
Reviewed-on: #1
Co-authored-by: Egor Pozharov <chedius232@gmail.com>
2026-10-07 19:49:10 +00:00

67 lines
6.2 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// Local, in-memory review environment. It serves the production build and
// fictional API data on its own loopback port, never contacts the real backend.
import { createServer } from 'node:http';
import { readFile } from 'node:fs/promises';
import { fileURLToPath } from 'node:url';
import { resolve, sep } from 'node:path';
import { randomUUID } from 'node:crypto';
const root = resolve(fileURLToPath(new URL('../dist/', import.meta.url)));
const dateKey = date => [date.getFullYear(), String(date.getMonth() + 1).padStart(2, '0'), String(date.getDate()).padStart(2, '0')].join('-');
const today = dateKey(new Date());
const tomorrowDate = new Date(); tomorrowDate.setDate(tomorrowDate.getDate() + 1);
const tomorrow = dateKey(tomorrowDate);
const base = { date: today, pickup_location: 'symbat', pickup_location_2: null, warehouse_request_source: null, warehouse_request_source_2: null, product_name_2: null, address: 'Кокшетау, Тестовая улица, 1', street: 'Тестовая', house: '1', apartment: '12', entrance: '2', floor: '4', phone: '+7 (000) 000-00-00', additional_phone: null, has_elevator: true, payment_status: 'prepaid', service_info: null, comment: '', status: 'new', created_at: new Date().toISOString(), updated_at: new Date().toISOString() };
let deliveries = [
{ ...base, id: 'demo-1', customer_name: 'Демо-клиент Анна', product_name: 'Диван «Комфорт»', service_info: 'Сборка после доставки', comment: 'Позвонить за 30 минут до приезда.' },
{ ...base, id: 'demo-2', customer_name: 'Демо-клиент Иван', product_name: 'Обеденный стол и четыре стула', street: 'Примерная', house: '24А', apartment: null, entrance: '1', floor: '2', pickup_location: 'warehouse', warehouse_request_source: 'nursaya', has_elevator: false, payment_status: 'paid' },
{ ...base, id: 'demo-3', customer_name: 'Демо-клиент Мария', product_name: 'Кровать и матрас', street: 'Учебная', house: '7', status: 'delivered', payment_status: 'paid' },
{ ...base, id: 'demo-4', date: tomorrow, customer_name: 'Демо-клиент Алексей', product_name: 'Шкаф', pickup_location: 'galaktika' },
];
deliveries = deliveries.map(d => ({ ...d, address: `Кокшетау, ул. ${d.street}, д. ${d.house}` }));
let failNext = false;
const sleep = ms => new Promise(resolve => setTimeout(resolve, ms));
const json = (response, status, data) => { response.writeHead(status, { 'Content-Type': 'application/json', 'Cache-Control': 'no-store' }); response.end(JSON.stringify(data)); };
const toDate = value => { const [day, month, year] = value.split('-'); return `${year}-${month}-${day}`; };
const server = createServer(async (request, response) => {
try {
const url = new URL(request.url, 'http://localhost');
if (url.pathname.startsWith('/api/') || url.pathname === '/__demo__/fail-next') {
let body = ''; for await (const chunk of request) body += chunk;
const data = body ? JSON.parse(body) : {};
if (url.pathname === '/__demo__/fail-next') { failNext = true; return json(response, 200, { ok: true }); }
if (url.pathname === '/api/auth/login') {
if (data.username !== 'demo' || data.password !== 'demo123') return json(response, 401, { error: 'Демо-вход: demo / demo123' });
return json(response, 200, { token: `demo.${Buffer.from(JSON.stringify({ sub: 'preview-demo-user' })).toString('base64')}.demo` });
}
if (url.pathname === '/api/ws') return json(response, 501, { error: 'No websocket in demo' });
if (request.method === 'GET' && url.pathname === '/api/deliveries/count') {
const dates = [...new Set(deliveries.map(d => d.date))];
return json(response, 200, { counts: dates.map(date => ({ date, count: deliveries.filter(d => d.date === date).length })) });
}
if (request.method === 'GET' && url.pathname === '/api/deliveries') return json(response, 200, { deliveries: deliveries.filter(d => d.date === toDate(url.searchParams.get('date'))) });
await sleep(500);
if (failNext) { failNext = false; return json(response, 500, { error: 'Simulated failure' }); }
if (request.method === 'POST' && url.pathname === '/api/deliveries') {
const id = randomUUID(); deliveries.push({ ...base, ...data, id, date: toDate(data.date) }); return json(response, 200, { id, message: 'created' });
}
const id = url.pathname.split('/')[3];
const existing = deliveries.find(d => d.id === id);
if (!existing) return json(response, 404, { error: 'Not found' });
if (request.method === 'DELETE') deliveries = deliveries.filter(d => d.id !== id);
else if (url.pathname.endsWith('/status')) existing.status = existing.status === 'new' ? 'delivered' : 'new';
else Object.assign(existing, data, { date: toDate(data.date), updated_at: new Date().toISOString() });
return json(response, 200, { message: 'ok' });
}
if (url.pathname === '/sw.js') { response.writeHead(404); return response.end(); }
const path = resolve(root, `.${decodeURIComponent(url.pathname === '/' ? '/index.html' : url.pathname)}`);
if (!path.startsWith(root + sep)) { response.writeHead(403); return response.end(); }
let content = await readFile(path);
const type = path.endsWith('.html') ? 'text/html' : path.endsWith('.js') ? 'text/javascript' : path.endsWith('.css') ? 'text/css' : path.endsWith('.svg') ? 'image/svg+xml' : path.endsWith('.json') ? 'application/json' : 'application/octet-stream';
if (type === 'text/html') content = Buffer.from(content.toString().replace('<body>', '<body><div style="background:#fff1e5;color:#6e3900;padding:8px 16px;text-align:center;font:13px system-ui">Демо-просмотр · Вымышленные заявки · Изменения не затрагивают рабочую базу</div>'));
response.writeHead(200, { 'Content-Type': type, 'Cache-Control': 'no-store' }); response.end(content);
} catch { response.writeHead(404); response.end('Not found'); }
});
server.listen(4174, '127.0.0.1', () => console.log('Demo UI: http://127.0.0.1:4174 — login demo / demo123'));
process.on('SIGINT', () => server.close(() => process.exit(0)));