Lead Radar: исходный код

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
yuriandClaude Opus 5.5 committed 2026-10-25 23:11:04 +04:00
commit 9749da5ae7
201 files changed
+97685

No files matched your search

+28
View File
@@ -0,0 +1,28 @@
# Настройки боевого стенда. Файл лежит на VPS: /opt/leadradar/.env (в репозиторий не попадает).
# Пароль базы: придумайте длинный случайный, например `openssl rand -hex 24`
POSTGRES_PASSWORD=
# Адрес кабинета — по нему строятся ссылки-приглашения, кнопки бота и Mini App. Обязательно https.
APP_URL=https://listcrm.yandpconsulting.com
# Боевой бот (@BotFather). В BotFather для него нужно задать домен: /setdomain → домен кабинета.
TELEGRAM_BOT_TOKEN=
TELEGRAM_BOT_USERNAME=
# Клиентский бот — анкета соискателя (отдельный бот в @BotFather). Без токена анкета выключена.
CLIENT_BOT_TOKEN=
CLIENT_BOT_USERNAME=
# Страница подборки для клиента: сколько процентов высоты срезать сверху у фото
CROP_TOP_PERCENT=8
# Ключ OpenRouter: с ним текст объекта дополняет модель; без него текст собирается по шаблону
OPENROUTER_API_KEY=
OPENROUTER_MODEL=
# Парсер. Ключ Jina можно также вставить в админке: Парсер → Настройки.
LISTING_SOURCE=listam
JINA_API_KEY=
# Сеть Nginx Proxy Manager на VPS (deploy.sh определяет её сам)
NPM_NETWORK=proxy
+23
View File
@@ -0,0 +1,23 @@
# Один файл — два образа: api (бэкенд, бот, парсер) и web (кабинет и Mini App за nginx).
FROM node:22-alpine AS base
RUN corepack enable
WORKDIR /app
COPY package.json pnpm-lock.yaml pnpm-workspace.yaml tsconfig.base.json ./
COPY apps/api/package.json apps/api/
COPY apps/web/package.json apps/web/
COPY packages/shared/package.json packages/shared/
RUN pnpm install --frozen-lockfile
COPY . .
FROM base AS webbuild
RUN pnpm --filter @lr/web build
FROM nginx:alpine AS web
COPY deploy/nginx.conf /etc/nginx/conf.d/default.conf
COPY --from=webbuild /app/apps/web/dist /usr/share/nginx/html
FROM base AS api
ENV NODE_ENV=production
WORKDIR /app/apps/api
# Сначала миграции базы, затем сервис.
CMD ["sh", "-c", "node --import tsx src/db/migrate.ts && exec node --import tsx src/main.ts"]
+92
View File
@@ -0,0 +1,92 @@
# Боевой стенд на VPS. Запускается скриптом ../deploy.sh; настройки — в /opt/leadradar/.env.
name: leadradar
services:
postgres:
image: postgres:16-alpine
container_name: leadradar-db
restart: unless-stopped
environment:
POSTGRES_USER: leadradar
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:?задайте POSTGRES_PASSWORD}
POSTGRES_DB: leadradar
volumes:
- pgdata:/var/lib/postgresql/data
healthcheck:
test: ['CMD-SHELL', 'pg_isready -U leadradar -d leadradar']
interval: 5s
timeout: 3s
retries: 30
networks: [internal]
api:
build: { context: .., dockerfile: deploy/Dockerfile, target: api }
container_name: leadradar-api
restart: unless-stopped
depends_on:
postgres: { condition: service_healthy }
environment:
DATABASE_URL: postgres://leadradar:${POSTGRES_PASSWORD}@postgres:5432/leadradar
API_PORT: 3100
APP_URL: ${APP_URL:?задайте APP_URL}
TELEGRAM_BOT_TOKEN: ${TELEGRAM_BOT_TOKEN:-}
TELEGRAM_BOT_USERNAME: ${TELEGRAM_BOT_USERNAME:-}
CLIENT_BOT_TOKEN: ${CLIENT_BOT_TOKEN:-}
CLIENT_BOT_USERNAME: ${CLIENT_BOT_USERNAME:-}
CROP_TOP_PERCENT: ${CROP_TOP_PERCENT:-8}
MEDIA_DIR: /app/data/media
OPENROUTER_API_KEY: ${OPENROUTER_API_KEY:-}
OPENROUTER_MODEL: ${OPENROUTER_MODEL:-}
LISTING_SOURCE: ${LISTING_SOURCE:-listam}
JINA_API_KEY: ${JINA_API_KEY:-}
volumes:
- media:/app/data/media
networks: [internal]
web:
build: { context: .., dockerfile: deploy/Dockerfile, target: web }
container_name: leadradar-web
restart: unless-stopped
depends_on: [api]
ports:
- '${WEB_PORT:-127.0.0.1:8090}:80'
networks: [internal, npm]
# Ежедневная резервная копия базы (ТЗ, раздел 9): хранится 14 последних.
backup:
image: postgres:16-alpine
container_name: leadradar-backup
restart: unless-stopped
depends_on:
postgres: { condition: service_healthy }
environment:
PGPASSWORD: ${POSTGRES_PASSWORD}
volumes:
- ${BACKUP_DIR:-../../backups}:/backups
entrypoint:
- sh
- -c
- |
set -o pipefail
sleep 120
while true; do
f=/backups/leadradar-$$(date +%Y-%m-%d).sql.gz
if [ ! -s "$$f" ]; then
pg_dump -h postgres -U leadradar leadradar | gzip > "$$f.tmp" && mv "$$f.tmp" "$$f" && echo "копия: $$f"
ls -1t /backups/leadradar-*.sql.gz | tail -n +15 | xargs -r rm --
fi
sleep 3600
done
networks: [internal]
volumes:
pgdata:
# Обрезанные фото для страниц клиентов: можно удалить — соберутся заново.
media:
networks:
internal:
# Сеть Nginx Proxy Manager: через неё NPM видит контейнер leadradar-web.
npm:
external: true
name: ${NPM_NETWORK:-proxy}
+32
View File
@@ -0,0 +1,32 @@
server {
listen 80;
server_name _;
root /usr/share/nginx/html;
index index.html;
gzip on;
gzip_types text/css application/javascript application/json image/svg+xml;
# API, бот и webhook платёжного провайдера.
location /api/ {
proxy_pass http://api:3100;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $http_x_forwarded_proto;
proxy_read_timeout 120s;
# Суперадмин отправляет в поддержке файлы до 20 МБ.
client_max_body_size 25m;
}
# Файлы сборки с хешем в имени меняются только вместе с именем.
location /assets/ {
expires 1y;
add_header Cache-Control "public, immutable";
}
# Кабинет — одностраничное приложение: любой адрес отдаёт index.html.
location / {
add_header Cache-Control "no-cache";
try_files $uri /index.html;
}
}