From dda917fc71e4c263ee3dbb7d610f28afb9095b42 Mon Sep 17 00:00:00 2001 From: zabudko-ru Date: Wed, 21 Oct 2026 04:22:36 +0400 Subject: [PATCH] Prod: new admin on admin.*, old admin moves to admin.*/old/ Dockerfile builds apps/backoffice into /srv/admin and the old admin with base /old/; nginx serves /old/ with its own uncached index.html. Worker notification links point to the new admin pages (moves, requests, chats); BACKOFFICE_URL defaults to ADMIN_URL. Co-Authored-By: Claude Opus 5.5 (1M context) --- apps/admin/vite.config.ts | 2 ++ apps/worker/src/index.ts | 9 +++++---- infra/prod/Dockerfile | 7 +++++-- infra/prod/env.example | 4 ++-- infra/prod/nginx.conf | 10 ++++++++++ 5 files changed, 24 insertions(+), 8 deletions(-) diff --git a/apps/admin/vite.config.ts b/apps/admin/vite.config.ts index a6a6aa7..791fd63 100644 --- a/apps/admin/vite.config.ts +++ b/apps/admin/vite.config.ts @@ -2,7 +2,9 @@ import react from '@vitejs/plugin-react'; import { defineConfig } from 'vite'; // В разработке API проксируется — Mini App и API на одном origin (удобно и для туннеля https) +// ADMIN_BASE=/old/ — в продакшене старая админка живёт по адресу admin.barevclub.com/old/ export default defineConfig({ + base: process.env.ADMIN_BASE ?? '/', plugins: [react()], server: { host: true, diff --git a/apps/worker/src/index.ts b/apps/worker/src/index.ts index dd96446..10902f3 100644 --- a/apps/worker/src/index.ts +++ b/apps/worker/src/index.ts @@ -102,11 +102,12 @@ async function runBroadcast(id: string) { } /** Новая админка, если уже на сервере; иначе — раздел переписки в старой */ -const BACKOFFICE_URL = process.env.BACKOFFICE_URL ?? ''; +// новая админка живёт на admin.* (старая — admin.*/old/), отдельный адрес не обязателен +const BACKOFFICE_URL = process.env.BACKOFFICE_URL || ADMIN_URL; const chatButton = (threadId: string | null, peer: string): InlineButton | null => BACKOFFICE_URL.startsWith('https://') && threadId ? { text: adminT('bot.admin.open'), url: `${BACKOFFICE_URL}/#/chats/${threadId}` } - : adminButton(`/concierge?${peer.startsWith('c:') ? 'client' : 'provider'}=${peer.slice(2)}`); + : adminButton('/chats'); const who = (u: { firstName: string | null; lastName?: string | null; username: string | null }) => `${escapeHtml(`${u.firstName ?? ''} ${u.lastName ?? ''}`.trim())}${u.username ? ` (@${escapeHtml(u.username)})` : ''}`; @@ -395,7 +396,7 @@ async function handle(event: DomainEvent) { const pkg = lead.package ? adminT(`landing.quiz.package_${lead.package}`) : '—'; await toAdmins( `🧳 ${adminT('bot.admin.newLead', { number: lead.number })}\n${escapeHtml(lead.name)} · ${escapeHtml(lead.contact)}\n${when} · ${pkg}${lead.adminNote ? `\n${escapeHtml(lead.adminNote)}` : ''}`, - compactRows([[adminButton('/leads')]]), + compactRows([[adminButton('/moves')]]), ); return; } @@ -405,7 +406,7 @@ async function handle(event: DomainEvent) { const what = r.kind === 'SUBSCRIPTION' ? adminT('admin.requests.subscription', { plan: r.plan === 'ELITE' ? 'Barev Elite' : 'Barev+' }) : adminT(`services.${r.serviceCode}.name`); await toAdmins( `⭐ ${adminT('bot.admin.newRequest', { number: r.number })}\n${who(r.client.user)}\n${escapeHtml(what)}${r.comment ? `\n${escapeHtml(r.comment)}` : ''}`, - compactRows([[{ text: adminT('bot.admin.reply'), callback_data: `ar:c:${r.clientId}` }, adminButton('/app-requests')]]), + compactRows([[{ text: adminT('bot.admin.reply'), callback_data: `ar:c:${r.clientId}` }, adminButton('/requests')]]), ); return; } diff --git a/infra/prod/Dockerfile b/infra/prod/Dockerfile index 3f919a6..4d45d7a 100644 --- a/infra/prod/Dockerfile +++ b/infra/prod/Dockerfile @@ -1,6 +1,7 @@ # Barev Club — образы для продакшена. # app — Node: API + воркер + боты (один процесс) и лендинг Next.js (тот же образ) # web — nginx: статика Mini App и админки + прокси /trpc и /files на API +# admin.* — новая админка (apps/backoffice), admin.*/old/ — старая (apps/admin) FROM node:22-alpine AS deps RUN corepack enable WORKDIR /repo @@ -15,13 +16,15 @@ ARG CLIENT_BOT_USERNAME=barevclub_bot ENV VITE_LANDING_URL=$LANDING_URL NEXT_PUBLIC_API_URL=$API_PUBLIC_URL CLIENT_BOT_USERNAME=$CLIENT_BOT_USERNAME NEXT_TELEMETRY_DISABLED=1 RUN pnpm --filter @barev/miniapp-client build \ && pnpm --filter @barev/miniapp-pro build \ - && pnpm --filter @barev/admin build \ + && ADMIN_BASE=/old/ pnpm --filter @barev/admin build \ + && VITE_OLD_ADMIN_URL=/old/ pnpm --filter @barev/backoffice build \ && pnpm --filter @barev/landing build FROM nginx:alpine AS web COPY --from=build /repo/apps/miniapp-client/dist /srv/client COPY --from=build /repo/apps/miniapp-pro/dist /srv/pro -COPY --from=build /repo/apps/admin/dist /srv/admin +COPY --from=build /repo/apps/backoffice/dist /srv/admin +COPY --from=build /repo/apps/admin/dist /srv/admin/old COPY infra/prod/nginx.conf /etc/nginx/conf.d/default.conf FROM node:22-alpine AS app diff --git a/infra/prod/env.example b/infra/prod/env.example index cfa7d51..8ba6dc4 100644 --- a/infra/prod/env.example +++ b/infra/prod/env.example @@ -16,8 +16,8 @@ LANDING_URL=https://barevclub.com CLIENT_MINIAPP_URL=https://app.barevclub.com PRO_MINIAPP_URL=https://pro.barevclub.com ADMIN_URL=https://admin.barevclub.com -# новая админка — когда выложим: тогда «Открыть» в уведомлениях о сообщениях ведёт в её чаты -# BACKOFFICE_URL=https://team.barevclub.com +# новая админка — на ADMIN_URL (старая — ADMIN_URL/old/); BACKOFFICE_URL нужен, только если она на другом адресе +# BACKOFFICE_URL= # серверный адрес API для лендинга (внутри docker-сети) API_URL=http://barev-app:4000 API_PORT=4000 diff --git a/infra/prod/nginx.conf b/infra/prod/nginx.conf index 22a1433..86a5f84 100644 --- a/infra/prod/nginx.conf +++ b/infra/prod/nginx.conf @@ -75,6 +75,16 @@ server { add_header Cache-Control "no-cache"; } + # старая админка: admin.barevclub.com/old/ — своя index.html, тоже без кеша + location ^~ /old/ { + root /srv/$barev_site; + try_files $uri $uri/ /old/index.html; + location ~ \.html$ { + root /srv/$barev_site; + add_header Cache-Control "no-cache"; + } + } + # статика с хешами в имени — кешируем надолго location /assets/ { root /srv/$barev_site;