Lead Radar: исходный код
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
commit
9749da5ae7
201 files changed
+97685
No files matched your search
@@ -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
|
||||
@@ -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"]
|
||||
@@ -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}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user