diff --git a/backend/src/routes/admin.ts b/backend/src/routes/admin.ts index e5223a1..c543eb1 100644 --- a/backend/src/routes/admin.ts +++ b/backend/src/routes/admin.ts @@ -111,7 +111,7 @@ adminRouter.post('/products', requireAdmin, async (req, res) => { }); adminRouter.put('/products/:id', requireAdmin, async (req, res) => { - const id = parseInt(req.params.id, 10); + const id = parseInt(String(req.params.id), 10); if (Number.isNaN(id)) { res.status(400).json({ error: 'invalid_id' }); return; @@ -152,7 +152,7 @@ adminRouter.put('/products/:id', requireAdmin, async (req, res) => { }); adminRouter.delete('/products/:id', requireAdmin, async (req, res) => { - const id = parseInt(req.params.id, 10); + const id = parseInt(String(req.params.id), 10); await query(`DELETE FROM products WHERE id = $1`, [id]); res.json({ ok: true }); }); @@ -192,7 +192,7 @@ adminRouter.get('/approach', requireAdmin, async (_req, res) => { }); adminRouter.put('/approach/:id', requireAdmin, async (req, res) => { - const id = parseInt(req.params.id, 10); + const id = parseInt(String(req.params.id), 10); const { title, description, icon_key, sort_order, is_active } = req.body; const updated = await queryOne( `UPDATE approach_items SET diff --git a/docker-compose.yaml b/docker-compose.yaml new file mode 100644 index 0000000..b9fec18 --- /dev/null +++ b/docker-compose.yaml @@ -0,0 +1,66 @@ +services: + app: + image: 'umbyte-landing:latest' + container_name: app-lwpkqvcz4vvmw93elb7s8mgh + restart: unless-stopped + depends_on: + - postgres + expose: + - '80' + networks: + coolify: + aliases: + - app-lwpkqvcz4vvmw93elb7s8mgh + environment: + NODE_ENV: production + PORT: 3000 + DB_HOST: postgres-lwpkqvcz4vvmw93elb7s8mgh + DB_PORT: 5432 + DB_USER: umbyte + DB_PASSWORD: 5816e45c1052d6b0b2c7f9be3103069d + DB_NAME: umbyte + JWT_SECRET: 138767b918eefb472dd8a8e884b1e8dda2d06646ebd03a9ef33a0cdbbf382d84 + ADMIN_INITIAL_PASSWORD: 80c106e53b6a0b75e74aa619 + labels: + - traefik.enable=true + - traefik.http.middlewares.gzip.compress=true + - traefik.http.middlewares.redirect-to-https.redirectscheme.scheme=https + - traefik.http.routers.http-0-umbyte.entryPoints=http + - traefik.http.routers.http-0-umbyte.middlewares=redirect-to-https + - 'traefik.http.routers.http-0-umbyte.rule=Host(`umbyte.ru`) && PathPrefix(`/`)' + - traefik.http.routers.http-0-umbyte.service=http-0-umbyte + - traefik.http.routers.https-0-umbyte.entryPoints=https + - traefik.http.routers.https-0-umbyte.middlewares=gzip + - 'traefik.http.routers.https-0-umbyte.rule=Host(`umbyte.ru`) && PathPrefix(`/`)' + - traefik.http.routers.https-0-umbyte.service=https-0-umbyte + - traefik.http.routers.https-0-umbyte.tls.certresolver=letsencrypt + - traefik.http.routers.https-0-umbyte.tls=true + - traefik.http.services.http-0-umbyte.loadbalancer.server.port=80 + - traefik.http.services.https-0-umbyte.loadbalancer.server.port=80 + + postgres: + image: postgres:16-alpine + container_name: postgres-lwpkqvcz4vvmw93elb7s8mgh + restart: unless-stopped + environment: + POSTGRES_DB: umbyte + POSTGRES_USER: umbyte + POSTGRES_PASSWORD: 5816e45c1052d6b0b2c7f9be3103069d + volumes: + - umbyte-pgdata:/var/lib/postgresql/data + networks: + coolify: + aliases: + - postgres-lwpkqvcz4vvmw93elb7s8mgh + healthcheck: + test: ['CMD-SHELL', 'pg_isready -U umbyte'] + interval: 10s + timeout: 5s + retries: 5 + +networks: + coolify: + external: true + +volumes: + umbyte-pgdata: diff --git a/frontend/src/pages/Landing.tsx b/frontend/src/pages/Landing.tsx index 073e602..ee6a22e 100644 --- a/frontend/src/pages/Landing.tsx +++ b/frontend/src/pages/Landing.tsx @@ -40,6 +40,15 @@ export function Landing() { const cta = content.sections.cta as Record; const settings = content.settings; + // Реальное количество продуктов из БД + русское склонение + const productCount = content.products.length; + const pluralProducts = (n: number) => { + const m10 = n % 10, m100 = n % 100; + if (m10 === 1 && m100 !== 11) return 'ФЛАГМАНСКИЙ ПРОДУКТ'; + if (m10 >= 2 && m10 <= 4 && !(m100 >= 12 && m100 <= 14)) return 'ФЛАГМАНСКИХ ПРОДУКТА'; + return 'ФЛАГМАНСКИХ ПРОДУКТОВ'; + }; + return (
@@ -169,10 +183,23 @@ export function Landing() {

{cta?.title}

{cta?.description}

+ {settings.contact_phone && ( + + )} {settings.contact_email && ( - + )}