diff --git a/.env.example b/.env.example index ef9a793..069ba3f 100644 --- a/.env.example +++ b/.env.example @@ -1,30 +1,58 @@ -# Telegram Bot Configuration +# Telegram Tinder Bot Configuration +# Rename this file to .env before starting the application + +# === REQUIRED SETTINGS === + +# Telegram Bot Token (Get from @BotFather) TELEGRAM_BOT_TOKEN=your_telegram_bot_token_here # Database Configuration +# For local development (when running the bot directly) DB_HOST=localhost DB_PORT=5432 DB_NAME=telegram_tinder_bot DB_USERNAME=postgres DB_PASSWORD=your_password_here -# Application Settings +# === APPLICATION SETTINGS === + +# Environment (development, production) NODE_ENV=development + +# Port for health checks PORT=3000 -# Optional: Redis for caching (if using) -REDIS_HOST=localhost -REDIS_PORT=6379 -REDIS_PASSWORD= +# === FILE UPLOAD SETTINGS === -# Optional: File upload settings +# Path for storing uploaded files UPLOAD_PATH=./uploads + +# Maximum file size for uploads (in bytes, default: 5MB) MAX_FILE_SIZE=5242880 -# Optional: External services -GOOGLE_MAPS_API_KEY=your_google_maps_key -CLOUDINARY_URL=your_cloudinary_url +# === LOGGING === -# Security +# Log level (error, warn, info, debug) +LOG_LEVEL=info + +# Path for storing log files +LOG_PATH=./logs + +# === SECURITY === + +# Secret key for JWT tokens JWT_SECRET=your_jwt_secret_here + +# Encryption key for sensitive data ENCRYPTION_KEY=your_encryption_key_here + +# === ADVANCED SETTINGS === + +# Notification check interval in milliseconds (default: 60000 - 1 minute) +NOTIFICATION_CHECK_INTERVAL=60000 + +# Number of matches to show per page +MATCHES_PER_PAGE=10 + +# Number of profiles to load at once +PROFILES_BATCH_SIZE=5 diff --git a/.env.production b/.env.production new file mode 100644 index 0000000..879c206 --- /dev/null +++ b/.env.production @@ -0,0 +1,68 @@ +# Конфигурация Telegram Tinder Bot для Production + +# === НЕОБХОДИМЫЕ НАСТРОЙКИ === + +# Токен Telegram бота (получить у @BotFather) +TELEGRAM_BOT_TOKEN=your_bot_token_here + +# Настройки базы данных PostgreSQL +DB_HOST=db +DB_PORT=5432 +DB_NAME=telegram_tinder_bot +DB_USERNAME=postgres +DB_PASSWORD=your_secure_password_here + +# === НАСТРОЙКИ ПРИЛОЖЕНИЯ === + +# Окружение +NODE_ENV=production + +# Порт для проверок работоспособности +PORT=3000 + +# === НАСТРОЙКИ ЗАГРУЗКИ ФАЙЛОВ === + +# Путь для хранения загруженных файлов +UPLOAD_PATH=./uploads + +# Максимальный размер загружаемого файла (в байтах, по умолчанию: 5MB) +MAX_FILE_SIZE=5242880 + +# Разрешенные типы файлов +ALLOWED_FILE_TYPES=image/jpeg,image/png,image/gif + +# === ЛОГИРОВАНИЕ === + +# Уровень логирования (error, warn, info, debug) +LOG_LEVEL=info + +# Путь для хранения лог-файлов +LOG_PATH=./logs + +# === БЕЗОПАСНОСТЬ === + +# Секретный ключ для JWT токенов +JWT_SECRET=your_jwt_secret_here + +# Ключ шифрования для чувствительных данных +ENCRYPTION_KEY=your_encryption_key_here + +# === РАСШИРЕННЫЕ НАСТРОЙКИ === + +# Интервал проверки уведомлений в миллисекундах (по умолчанию: 60000 - 1 минута) +NOTIFICATION_CHECK_INTERVAL=60000 + +# Количество матчей для отображения на странице +MATCHES_PER_PAGE=10 + +# Количество профилей для загрузки за один раз +PROFILES_BATCH_SIZE=5 + +# === НАСТРОЙКИ DOCKER === + +# Имя хоста для доступа извне +EXTERNAL_HOSTNAME=your_domain.com + +# Настройки кеширования (Redis, если используется) +CACHE_HOST=redis +CACHE_PORT=6379 diff --git a/Dockerfile b/Dockerfile index dbb2620..05edabe 100644 --- a/Dockerfile +++ b/Dockerfile @@ -8,11 +8,12 @@ WORKDIR /app COPY package*.json ./ COPY tsconfig.json ./ -# Install dependencies -RUN npm ci --only=production && npm cache clean --force +# Install all dependencies (including devDependencies for build) +RUN npm ci && npm cache clean --force # Copy source code COPY src/ ./src/ +COPY .env.example ./ # Build the application RUN npm run build @@ -31,11 +32,19 @@ RUN npm ci --only=production && npm cache clean --force # Copy built application from builder stage COPY --from=builder /app/dist ./dist +COPY --from=builder /app/.env.example ./.env.example -# Copy configuration files -COPY config/ ./config/ +# Copy database migrations +COPY src/database/migrations/ ./dist/database/migrations/ -# Create uploads directory +# Copy locales +COPY src/locales/ ./dist/locales/ + +# Copy scripts +COPY scripts/startup.sh ./startup.sh +RUN chmod +x ./startup.sh + +# Create directories RUN mkdir -p uploads logs # Create non-root user for security @@ -53,7 +62,7 @@ EXPOSE 3000 # Health check HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \ - CMD node -e "require('http').get('http://localhost:3000/health', (res) => { process.exit(res.statusCode === 200 ? 0 : 1) })" || exit 1 + CMD wget --no-verbose --tries=1 --spider http://localhost:3000/health || exit 1 -# Start the application -CMD ["node", "dist/bot.js"] +# Start the application with migration script +CMD ["./startup.sh"] diff --git a/README.md b/README.md index edc7413..f465430 100644 --- a/README.md +++ b/README.md @@ -268,8 +268,32 @@ npm run dev - Node.js 16+ - PostgreSQL 12+ - Telegram Bot Token (получить у [@BotFather](https://t.me/BotFather)) +- Docker и Docker Compose (опционально) -### 2. Установка +### 2. Установка и запуск + +#### С использованием стартовых скриптов (рекомендуется) + +```bash +# Клонировать репозиторий +git clone +cd telegram-tinder-bot + +# На Windows: +.\start.bat + +# На Linux/macOS: +chmod +x start.sh +./start.sh +``` + +Скрипт автоматически: +- Проверит наличие файла .env и создаст его из шаблона при необходимости +- Предложит выбор между запуском с локальной БД или подключением к внешней +- Настроит все необходимые параметры окружения +- Запустит контейнеры Docker + +#### Без Docker ```bash # Клонировать репозиторий @@ -279,24 +303,17 @@ cd telegram-tinder-bot # Установить зависимости npm install -# Скомпилировать TypeScript -npm run build -``` +# Скопировать файл конфигурации +cp .env.example .env +# Отредактируйте файл .env и укажите свой TELEGRAM_BOT_TOKEN -### 3. Настройка базы данных - -```bash # Создать базу данных PostgreSQL createdb telegram_tinder_bot # Запустить миграции -psql -d telegram_tinder_bot -f src/database/migrations/init.sql -``` +npm run migrate:up -### 4. Запуск бота - -```bash -# Компиляция TypeScript +# Скомпилировать TypeScript npm run build # Запуск бота diff --git a/bin/backup_db.sh b/bin/backup_db.sh new file mode 100644 index 0000000..117d106 --- /dev/null +++ b/bin/backup_db.sh @@ -0,0 +1,54 @@ +#!/bin/bash +# backup_db.sh - Script for backing up the PostgreSQL database + +echo "📦 Backing up PostgreSQL database..." + +# Default backup directory +BACKUP_DIR="${BACKUP_DIR:-/var/backups/tg_tinder_bot}" +BACKUP_FILENAME="tg_tinder_bot_$(date +%Y%m%d_%H%M%S).sql" +BACKUP_PATH="$BACKUP_DIR/$BACKUP_FILENAME" + +# Create backup directory if it doesn't exist +mkdir -p "$BACKUP_DIR" + +# Check if running in docker-compose environment +if [ -f /.dockerenv ] || [ -f /proc/self/cgroup ] && grep -q docker /proc/self/cgroup; then + echo "🐳 Running in Docker environment, using docker-compose exec..." + docker-compose exec -T db pg_dump -U postgres telegram_tinder_bot > "$BACKUP_PATH" +else + # Check if PGPASSWORD is set in environment + if [ -z "$PGPASSWORD" ]; then + # If .env file exists, try to get password from there + if [ -f .env ]; then + DB_PASSWORD=$(grep DB_PASSWORD .env | cut -d '=' -f2) + export PGPASSWORD="$DB_PASSWORD" + else + echo "⚠️ No DB_PASSWORD found in environment or .env file." + echo "Please enter PostgreSQL password:" + read -s PGPASSWORD + export PGPASSWORD + fi + fi + + echo "💾 Backing up database to $BACKUP_PATH..." + pg_dump -h localhost -U postgres -d telegram_tinder_bot > "$BACKUP_PATH" +fi + +# Check if backup was successful +if [ $? -eq 0 ]; then + echo "✅ Backup completed successfully: $BACKUP_PATH" + echo "📊 Backup size: $(du -h $BACKUP_PATH | cut -f1)" + + # Compress the backup + gzip -f "$BACKUP_PATH" + echo "🗜️ Compressed backup: $BACKUP_PATH.gz" + + # Keep only the last 7 backups + echo "🧹 Cleaning up old backups..." + find "$BACKUP_DIR" -name "tg_tinder_bot_*.sql.gz" -type f -mtime +7 -delete + + echo "🎉 Backup process completed!" +else + echo "❌ Backup failed!" + exit 1 +fi diff --git a/bin/create_release.sh b/bin/create_release.sh new file mode 100644 index 0000000..83397d1 --- /dev/null +++ b/bin/create_release.sh @@ -0,0 +1,72 @@ +#!/bin/bash +# Скрипт для создания релиза Telegram Tinder Bot + +# Получение версии из package.json +VERSION=$(grep -m1 "version" package.json | cut -d'"' -f4) +RELEASE_NAME="tg-tinder-bot-v$VERSION" +RELEASE_DIR="bin/releases/$RELEASE_NAME" + +echo "🚀 Создание релиза $RELEASE_NAME" + +# Создание директории релиза +mkdir -p "$RELEASE_DIR" + +# Очистка временных файлов +echo "🧹 Очистка временных файлов..." +rm -rf dist node_modules + +# Установка зависимостей +echo "📦 Установка зависимостей production..." +npm ci --only=production + +# Сборка проекта +echo "🔧 Сборка проекта..." +npm run build + +# Копирование файлов релиза +echo "📋 Копирование файлов..." +cp -r dist "$RELEASE_DIR/" +cp -r src/locales "$RELEASE_DIR/dist/" +cp package.json package-lock.json .env.example "$RELEASE_DIR/" +cp -r bin/start_bot.* bin/install_ubuntu.sh "$RELEASE_DIR/" +cp README.md LICENSE "$RELEASE_DIR/" 2>/dev/null || echo "Файлы документации не найдены" +cp sql/consolidated.sql "$RELEASE_DIR/" +cp docker-compose.yml Dockerfile "$RELEASE_DIR/" +cp deploy.sh "$RELEASE_DIR/" && chmod +x "$RELEASE_DIR/deploy.sh" + +# Создание README для релиза +cat > "$RELEASE_DIR/RELEASE.md" << EOL +# Telegram Tinder Bot v$VERSION + +Эта папка содержит релиз Telegram Tinder Bot версии $VERSION. + +## Содержимое + +- \`dist/\` - Скомпилированный код +- \`package.json\` - Зависимости и скрипты +- \`.env.example\` - Пример конфигурации +- \`docker-compose.yml\` и \`Dockerfile\` - Для запуска через Docker +- \`consolidated.sql\` - SQL-скрипт для инициализации базы данных +- \`deploy.sh\` - Скрипт для простого деплоя + +## Быстрый старт + +1. Создайте файл \`.env\` на основе \`.env.example\` +2. Запустите бота одним из способов: + - Через Docker: \`./deploy.sh\` + - Через Node.js: \`node dist/bot.js\` + +## Дата релиза + +$(date "+%d.%m.%Y %H:%M") +EOL + +# Архивирование релиза +echo "📦 Создание архива..." +cd bin/releases +zip -r "$RELEASE_NAME.zip" "$RELEASE_NAME" +cd ../.. + +echo "✅ Релиз создан успешно!" +echo "📂 Релиз доступен в: bin/releases/$RELEASE_NAME" +echo "📦 Архив релиза: bin/releases/$RELEASE_NAME.zip" diff --git a/bin/install_docker.sh b/bin/install_docker.sh new file mode 100644 index 0000000..3033222 --- /dev/null +++ b/bin/install_docker.sh @@ -0,0 +1,58 @@ +#!/bin/bash +# install_docker.sh - Script for installing Docker and Docker Compose + +echo "🚀 Installing Docker and Docker Compose..." + +# Check if script is run as root +if [ "$(id -u)" -ne 0 ]; then + echo "❌ This script must be run as root. Please run with sudo." + exit 1 +fi + +# Update package lists +echo "📦 Updating package lists..." +apt update + +# Install required packages +echo "📦 Installing required packages..." +apt install -y apt-transport-https ca-certificates curl software-properties-common + +# Add Docker GPG key +echo "🔑 Adding Docker GPG key..." +curl -fsSL https://download.docker.com/linux/ubuntu/gpg | apt-key add - + +# Add Docker repository +echo "📁 Adding Docker repository..." +add-apt-repository "deb [arch=amd64] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable" + +# Update package lists again +apt update + +# Install Docker +echo "🐳 Installing Docker..." +apt install -y docker-ce docker-ce-cli containerd.io + +# Enable and start Docker service +systemctl enable docker +systemctl start docker + +# Install Docker Compose +echo "🐳 Installing Docker Compose..." +curl -L "https://github.com/docker/compose/releases/download/v2.24.6/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose +chmod +x /usr/local/bin/docker-compose + +# Check versions +echo "✅ Installation complete!" +echo "Docker version:" +docker --version +echo "Docker Compose version:" +docker-compose --version + +# Add current user to docker group if not root +if [ -n "$SUDO_USER" ]; then + echo "👤 Adding user $SUDO_USER to docker group..." + usermod -aG docker $SUDO_USER + echo "⚠️ Please log out and log back in for group changes to take effect." +fi + +echo "🎉 Docker installation completed successfully!" diff --git a/check_schema.ts b/check_schema.ts deleted file mode 100644 index 3cc78f7..0000000 --- a/check_schema.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { query } from './src/database/connection'; - -async function checkSchema() { - try { - const result = await query('SELECT column_name, data_type FROM information_schema.columns WHERE table_name = $1', ['messages']); - console.log(result.rows); - process.exit(0); - } catch (error) { - console.error(error); - process.exit(1); - } -} - -checkSchema(); diff --git a/deploy.sh b/deploy.sh new file mode 100644 index 0000000..7091bb8 --- /dev/null +++ b/deploy.sh @@ -0,0 +1,55 @@ +#!/bin/bash +# deploy.sh - Скрипт для деплоя Telegram Tinder Bot + +echo "🚀 Деплой Telegram Tinder Bot..." + +# Проверяем наличие Docker +if ! command -v docker &> /dev/null || ! command -v docker-compose &> /dev/null; then + echo "❌ Docker и Docker Compose должны быть установлены!" + echo "Для установки на Ubuntu выполните:" + echo "sudo apt update && sudo apt install -y docker.io docker-compose" + exit 1 +fi + +# Определяем рабочую директорию +SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )" +cd "$SCRIPT_DIR" + +# Получаем последние изменения +echo "📥 Получение последних изменений..." +git pull origin main + +# Проверяем наличие .env файла +if [ ! -f .env ]; then + echo "📝 Создание .env файла из .env.production..." + cp .env.production .env + echo "⚠️ Пожалуйста, отредактируйте файл .env и укажите свои настройки!" + exit 1 +fi + +# Запускаем Docker Compose +echo "🐳 Сборка и запуск контейнеров Docker..." +docker-compose down +docker-compose build +docker-compose up -d + +# Проверяем статус контейнеров +echo "🔍 Проверка статуса контейнеров..." +docker-compose ps + +echo "✅ Деплой успешно завершен! Бот должен быть доступен через Telegram." +echo "" +echo "📊 Полезные команды:" +echo "- Просмотр логов: docker-compose logs -f" +echo "- Перезапуск сервисов: docker-compose restart" +echo "- Остановка всех сервисов: docker-compose down" +echo "- Доступ к базе данных: docker-compose exec db psql -U postgres -d telegram_tinder_bot" +echo "- Проверка состояния бота: curl http://localhost:3000/health" +echo "" +echo "🌟 Для администрирования базы данных:" +echo "Adminer доступен по адресу: http://ваш_сервер:8080" +echo " - Система: PostgreSQL" +echo " - Сервер: db" +echo " - Пользователь: postgres" +echo " - Пароль: (из переменной DB_PASSWORD в .env)" +echo " - База данных: telegram_tinder_bot" diff --git a/docker-compose.override.yml.example b/docker-compose.override.yml.example new file mode 100644 index 0000000..7ebf603 --- /dev/null +++ b/docker-compose.override.yml.example @@ -0,0 +1,69 @@ +# Используем версию Docker Compose для локальной разработки + +version: '3.8' + +services: + bot: + build: + context: . + dockerfile: Dockerfile + args: + - NODE_ENV=development + environment: + - NODE_ENV=development + - DB_HOST=db + - DB_PORT=5432 + - DB_NAME=telegram_tinder_bot + - DB_USERNAME=postgres + - DB_PASSWORD=dev_password + volumes: + # Монтируем исходный код для горячей перезагрузки + - ./src:/app/src + - ./dist:/app/dist + - ./.env:/app/.env + ports: + # Открываем порт для отладки + - "9229:9229" + command: npm run dev + networks: + - bot-network + depends_on: + - db + + db: + # Используем последнюю версию PostgreSQL для разработки + image: postgres:16-alpine + environment: + - POSTGRES_DB=telegram_tinder_bot + - POSTGRES_USER=postgres + - POSTGRES_PASSWORD=dev_password + volumes: + # Хранение данных локально для быстрого сброса + - postgres_data_dev:/var/lib/postgresql/data + # Монтируем скрипты инициализации + - ./sql:/docker-entrypoint-initdb.d + ports: + # Открываем порт для доступа к БД напрямую + - "5433:5432" + networks: + - bot-network + + adminer: + image: adminer:latest + ports: + - "8080:8080" + networks: + - bot-network + depends_on: + - db + environment: + - ADMINER_DEFAULT_SERVER=db + - ADMINER_DEFAULT_USER=postgres + - ADMINER_DEFAULT_PASSWORD=dev_password + +volumes: + postgres_data_dev: + +networks: + bot-network: + driver: bridge diff --git a/docker-compose.yml b/docker-compose.yml index 760e31c..272f4bb 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -6,19 +6,26 @@ services: container_name: telegram-tinder-bot restart: unless-stopped depends_on: - - db + db: + condition: service_healthy + env_file: .env environment: - NODE_ENV=production - - DB_HOST={DB_HOST} - - DB_PORT={DB_PORT} - - DB_NAME={DB_NAME} - - DB_USERNAME={DB_USERNAME} - - DB_PASSWORD={DB_PASSWORD} - - TELEGRAM_BOT_TOKEN={TELEGRAM_BOT_TOKEN} + - DB_HOST=db + - DB_PORT=5432 + - DB_NAME=telegram_tinder_bot + - DB_USERNAME=postgres volumes: - ./uploads:/app/uploads + - ./logs:/app/logs networks: - bot-network + healthcheck: + test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://localhost:3000/health"] + interval: 30s + timeout: 5s + retries: 3 + start_period: 10s db: image: postgres:15-alpine @@ -27,14 +34,18 @@ services: environment: - POSTGRES_DB=telegram_tinder_bot - POSTGRES_USER=postgres - - POSTGRES_PASSWORD=password123 + - POSTGRES_PASSWORD=${DB_PASSWORD:-password123} volumes: - postgres_data:/var/lib/postgresql/data - - ./src/database/migrations/init.sql:/docker-entrypoint-initdb.d/init.sql ports: - "5433:5432" networks: - bot-network + healthcheck: + test: ["CMD-SHELL", "pg_isready -U postgres"] + interval: 5s + timeout: 5s + retries: 5 adminer: image: adminer:latest diff --git a/docs/PRODUCTION_DEPLOYMENT.md b/docs/PRODUCTION_DEPLOYMENT.md new file mode 100644 index 0000000..ac43818 --- /dev/null +++ b/docs/PRODUCTION_DEPLOYMENT.md @@ -0,0 +1,264 @@ +# Инструкция по развертыванию Telegram Tinder Bot в Production + +Это подробное руководство по развертыванию Telegram Tinder Bot в production-окружении с использованием Docker и Docker Compose. + +## 📋 Требования + +- **Операционная система**: Ubuntu 20.04 или выше (рекомендуется) / Windows Server с Docker +- **Программное обеспечение**: + - Docker (последняя версия) + - Docker Compose (последняя версия) + - Git + +## 🚀 Быстрое развертывание + +### 1. Клонирование репозитория + +```bash +git clone https://github.com/your-username/telegram-tinder-bot.git +cd telegram-tinder-bot +``` + +### 2. Настройка конфигурации + +```bash +# Создание файла конфигурации из шаблона +cp .env.production .env + +# Редактирование конфигурационного файла +nano .env +``` + +Важно указать следующие параметры: +- `TELEGRAM_BOT_TOKEN`: токен от @BotFather +- `DB_PASSWORD`: надежный пароль для базы данных +- `JWT_SECRET`: случайная строка для JWT +- `ENCRYPTION_KEY`: случайная строка для шифрования + +### 3. Запуск деплоя + +```bash +# Сделайте скрипт исполняемым +chmod +x deploy.sh + +# Запустите деплой +./deploy.sh +``` + +## 🔧 Подробное руководство по установке + +### Подготовка сервера Ubuntu + +```bash +# Обновление системы +sudo apt update && sudo apt upgrade -y + +# Установка необходимых пакетов +sudo apt install -y apt-transport-https ca-certificates curl software-properties-common git + +# Установка Docker +curl -fsSL https://get.docker.com -o get-docker.sh +sudo sh get-docker.sh + +# Добавление текущего пользователя в группу docker +sudo usermod -aG docker ${USER} + +# Установка Docker Compose +sudo apt install -y docker-compose +``` + +### Клонирование и настройка проекта + +```bash +# Создание директории для проекта +mkdir -p /opt/telegram-tinder +cd /opt/telegram-tinder + +# Клонирование репозитория +git clone https://github.com/your-username/telegram-tinder-bot.git . + +# Настройка .env файла +cp .env.production .env +nano .env + +# Создание директорий для данных и логов +mkdir -p uploads logs +chmod 777 uploads logs +``` + +### Запуск проекта + +```bash +# Запуск в фоновом режиме +docker-compose up -d + +# Проверка статуса контейнеров +docker-compose ps + +# Просмотр логов +docker-compose logs -f +``` + +## 🔄 Обновление бота + +Для обновления бота выполните: + +```bash +cd /путь/к/telegram-tinder-bot +./deploy.sh +``` + +Скрипт автоматически выполнит: +1. Получение последних изменений из репозитория +2. Перезапуск контейнеров с новой версией кода +3. Применение миграций базы данных + +## 🛡️ Обеспечение безопасности + +### Настройка файрвола + +```bash +# Разрешение только необходимых портов +sudo ufw allow 22/tcp +sudo ufw allow 80/tcp +sudo ufw allow 443/tcp +sudo ufw enable +``` + +### Настройка HTTPS с Let's Encrypt (опционально) + +Для использования HTTPS с Let's Encrypt и Nginx: + +```bash +# Установка Certbot +sudo apt install -y certbot python3-certbot-nginx + +# Получение SSL-сертификата +sudo certbot --nginx -d your-domain.com +``` + +## 📊 Мониторинг и управление + +### Просмотр логов + +```bash +# Логи всех контейнеров +docker-compose logs -f + +# Логи конкретного контейнера (например, бота) +docker-compose logs -f bot + +# Последние 100 строк логов +docker-compose logs --tail=100 bot +``` + +### Управление сервисами + +```bash +# Остановка всех контейнеров +docker-compose down + +# Перезапуск всех контейнеров +docker-compose restart + +# Перезапуск только бота +docker-compose restart bot +``` + +### Доступ к базе данных + +```bash +# Вход в консоль PostgreSQL +docker-compose exec db psql -U postgres -d telegram_tinder_bot + +# Резервное копирование базы данных +docker-compose exec db pg_dump -U postgres telegram_tinder_bot > backup_$(date +%Y%m%d).sql + +# Восстановление базы из резервной копии +cat backup.sql | docker-compose exec -T db psql -U postgres -d telegram_tinder_bot +``` + +## 🔍 Устранение неполадок + +### Проверка работоспособности + +```bash +# Проверка API бота +curl http://localhost:3000/health + +# Проверка подключения к базе данных +docker-compose exec bot node -e "const { Client } = require('pg'); const client = new Client({ host: 'db', port: 5432, database: 'telegram_tinder_bot', user: 'postgres', password: process.env.DB_PASSWORD }); client.connect().then(() => { console.log('Connected to DB!'); client.end(); }).catch(e => console.error(e));" +``` + +### Общие проблемы и решения + +**Проблема**: Бот не отвечает в Telegram +**Решение**: +- Проверьте валидность токена бота +- Проверьте логи на наличие ошибок: `docker-compose logs -f bot` + +**Проблема**: Ошибки подключения к базе данных +**Решение**: +- Проверьте настройки подключения в `.env` +- Убедитесь, что контейнер с базой данных запущен: `docker-compose ps` +- Проверьте логи базы данных: `docker-compose logs db` + +**Проблема**: Недостаточно свободного места на диске +**Решение**: +- Очистите неиспользуемые Docker образы: `docker image prune -a` +- Очистите неиспользуемые Docker тома: `docker volume prune` + +## 🔁 Настройка автоматического обновления + +### Настройка автообновления через Cron + +```bash +# Редактирование crontab +crontab -e + +# Добавление задачи (обновление каждую ночь в 3:00) +0 3 * * * cd /путь/к/telegram-tinder-bot && ./deploy.sh > /tmp/tg-tinder-update.log 2>&1 +``` + +## 📝 Рекомендации по обслуживанию + +1. **Регулярное резервное копирование**: + ```bash + # Ежедневное резервное копирование через cron + 0 2 * * * docker-compose exec -T db pg_dump -U postgres telegram_tinder_bot > /path/to/backups/tg_$(date +\%Y\%m\%d).sql + ``` + +2. **Мониторинг использования ресурсов**: + ```bash + # Просмотр использования ресурсов контейнерами + docker stats + ``` + +3. **Обновление Docker образов**: + ```bash + # Обновление образов + docker-compose pull + docker-compose up -d + ``` + +4. **Проверка журналов на наличие ошибок**: + ```bash + # Поиск ошибок в логах + docker-compose logs | grep -i error + docker-compose logs | grep -i exception + ``` + +--- + +## 📋 Контрольный список деплоя + +- [ ] Установлены Docker и Docker Compose +- [ ] Клонирован репозиторий +- [ ] Настроен файл .env с реальными данными +- [ ] Запущены контейнеры через docker-compose +- [ ] Проверено подключение бота к Telegram API +- [ ] Настроено резервное копирование +- [ ] Настроен файрвол и безопасность сервера +- [ ] Проверены и настроены логи +- [ ] (Опционально) Настроен SSL для веб-интерфейса +- [ ] (Опционально) Настроено автоматическое обновление diff --git a/fixes.md b/fixes.md deleted file mode 100644 index ded6c38..0000000 --- a/fixes.md +++ /dev/null @@ -1,90 +0,0 @@ -# Исправления ошибок в коде - -## Проблема: Несоответствие имен столбцов в таблице swipes - -В коде обнаружены несоответствия в названиях столбцов при работе с таблицей `swipes`. Используются два разных варианта именования: - -1. `user_id` и `target_user_id` -2. `swiper_id` и `swiped_id` - -Судя по ошибкам в консоли и анализу кода, корректными именами столбцов являются `user_id` и `target_user_id`. - -## Необходимые исправления - -### 1. В файле `profileService.ts` - метод `deleteProfile`: - -```typescript -// Неверно: -await client.query('DELETE FROM swipes WHERE swiper_id = $1 OR swiped_id = $1', [userId]); - -// Исправить на: -await client.query('DELETE FROM swipes WHERE user_id = $1 OR target_user_id = $1', [userId]); -``` - -### 2. В файле `matchingService.ts` - метод `performSwipe`: - -```typescript -// Неверно: -const reciprocalSwipe = await client.query(` - SELECT * FROM swipes - WHERE swiper_id = $1 AND swiped_id = $2 AND direction IN ('right', 'super') -`, [targetUserId, userId]); - -// Исправить на: -const reciprocalSwipe = await client.query(` - SELECT * FROM swipes - WHERE user_id = $1 AND target_user_id = $2 AND direction IN ('right', 'super') -`, [targetUserId, userId]); -``` - -### 3. В файле `matchingService.ts` - метод `getRecentLikes`: - -```typescript -// Неверно (если используется метод mapEntityToSwipe): -private mapEntityToSwipe(entity: any): Swipe { - return new Swipe({ - id: entity.id, - userId: entity.swiper_id, - targetUserId: entity.swiped_id, - type: this.convertDirectionToSwipeType(entity.direction), - timestamp: entity.created_at, - isMatch: entity.is_match - }); -} - -// Исправить на: -private mapEntityToSwipe(entity: any): Swipe { - return new Swipe({ - id: entity.id, - userId: entity.user_id, - targetUserId: entity.target_user_id, - type: this.convertDirectionToSwipeType(entity.direction), - timestamp: entity.created_at, - isMatch: entity.is_match - }); -} -``` - -### 4. В файле `matchingService.ts` - метод `getDailySwipeStats`: - -```typescript -// Неверно: -const result = await query(` - SELECT direction, COUNT(*) as count - FROM swipes - WHERE swiper_id = $1 AND created_at >= $2 - GROUP BY direction -`, [userId, today]); - -// Исправить на: -const result = await query(` - SELECT direction, COUNT(*) as count - FROM swipes - WHERE user_id = $1 AND created_at >= $2 - GROUP BY direction -`, [userId, today]); -``` - -## Примечание - -После внесения исправлений рекомендуется проверить все остальные места в коде, где могут использоваться эти имена столбцов, и убедиться в их согласованности. diff --git a/init-notifications-db.js b/init-notifications-db.js deleted file mode 100644 index bcd29d0..0000000 --- a/init-notifications-db.js +++ /dev/null @@ -1,174 +0,0 @@ -require('dotenv').config(); -const { Pool } = require('pg'); -const { v4: uuidv4 } = require('uuid'); - -// Функция для запуска скрипта -async function initializeDatabase() { - console.log('Starting database initialization script...'); - - const pool = new Pool({ - host: 'localhost', // Используем localhost - port: 5432, // Используем стандартный порт 5432 - database: 'telegram_tinder_bot', - user: 'postgres', - password: '', - max: 5, - connectionTimeoutMillis: 5000 - }); - - console.log('DB Connection Details:'); - console.log('- Host: localhost'); - console.log('- Port: 5432'); - console.log('- Database: telegram_tinder_bot'); - console.log('- User: postgres'); - - try { - // Проверяем подключение - console.log('Testing connection...'); - const client = await pool.connect(); - - try { - console.log('✅ Connected to database successfully!'); - - // 1. Создаем расширение для генерации UUID если его нет - console.log('Creating UUID extension...'); - await client.query(`CREATE EXTENSION IF NOT EXISTS "uuid-ossp"`); - - // 2. Создаем таблицу для шаблонов уведомлений - console.log('Creating notification_templates table...'); - await client.query(` - CREATE TABLE IF NOT EXISTS notification_templates ( - id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), - type VARCHAR(50) NOT NULL UNIQUE, - title TEXT NOT NULL, - message_template TEXT NOT NULL, - button_template JSONB NOT NULL, - created_at TIMESTAMP DEFAULT NOW() - ) - `); - - // 3. Вставляем базовые шаблоны - console.log('Inserting notification templates...'); - - const templates = [ - { - id: uuidv4(), - type: 'new_like', - title: 'Новый лайк!', - message_template: '❤️ *{{name}}* поставил(а) вам лайк!\n\nВозраст: {{age}}\n{{city}}\n\nОтветьте взаимностью или посмотрите профиль.', - button_template: JSON.stringify({ - inline_keyboard: [ - [{ text: '👀 Посмотреть профиль', callback_data: 'view_profile:{{userId}}' }], - [ - { text: '❤️ Лайк в ответ', callback_data: 'like_back:{{userId}}' }, - { text: '⛔️ Пропустить', callback_data: 'dislike_profile:{{userId}}' } - ], - [{ text: '💕 Открыть все лайки', callback_data: 'view_likes' }] - ] - }) - }, - { - id: uuidv4(), - type: 'super_like', - title: 'Супер-лайк!', - message_template: '⭐️ *{{name}}* отправил(а) вам супер-лайк!\n\nВозраст: {{age}}\n{{city}}\n\nВы произвели особое впечатление! Ответьте взаимностью или посмотрите профиль.', - button_template: JSON.stringify({ - inline_keyboard: [ - [{ text: '👀 Посмотреть профиль', callback_data: 'view_profile:{{userId}}' }], - [ - { text: '❤️ Лайк в ответ', callback_data: 'like_back:{{userId}}' }, - { text: '⛔️ Пропустить', callback_data: 'dislike_profile:{{userId}}' } - ], - [{ text: '💕 Открыть все лайки', callback_data: 'view_likes' }] - ] - }) - }, - { - id: uuidv4(), - type: 'new_match', - title: 'Новый матч!', - message_template: '🎊 *Ура! Это взаимно!* 🎊\n\nВы и *{{name}}* понравились друг другу!\nВозраст: {{age}}\n{{city}}\n\nСделайте первый шаг - напишите сообщение!', - button_template: JSON.stringify({ - inline_keyboard: [ - [{ text: '💬 Начать общение', callback_data: 'open_native_chat_{{matchId}}' }], - [ - { text: '👀 Посмотреть профиль', callback_data: 'view_profile:{{userId}}' }, - { text: '📋 Все матчи', callback_data: 'native_chats' } - ] - ] - }) - }, - { - id: uuidv4(), - type: 'new_message', - title: 'Новое сообщение!', - message_template: '💌 *Новое сообщение!*\n\nОт: *{{name}}*\n\n"{{message}}"\n\nОтветьте на сообщение прямо сейчас!', - button_template: JSON.stringify({ - inline_keyboard: [ - [{ text: '📩 Ответить', callback_data: 'open_native_chat_{{matchId}}' }], - [ - { text: '👤 Профиль', callback_data: 'view_profile:{{userId}}' }, - { text: '📋 Все чаты', callback_data: 'native_chats' } - ] - ] - }) - } - ]; - - // Вставляем шаблоны с проверкой на конфликты - for (const template of templates) { - await client.query(` - INSERT INTO notification_templates - (id, type, title, message_template, button_template, created_at) - VALUES ($1, $2, $3, $4, $5, NOW()) - ON CONFLICT (type) DO UPDATE - SET title = $3, - message_template = $4, - button_template = $5 - `, [template.id, template.type, template.title, template.message_template, template.button_template]); - } - - console.log('✅ Notification templates created/updated successfully'); - - // 4. Создаем таблицу для хранения логов уведомлений если её нет - console.log('Creating notifications table...'); - await client.query(` - CREATE TABLE IF NOT EXISTS notifications ( - id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), - user_id UUID NOT NULL, - type VARCHAR(50) NOT NULL, - data JSONB NOT NULL, - is_read BOOLEAN DEFAULT FALSE, - created_at TIMESTAMP DEFAULT NOW() - ) - `); - - console.log('✅ Notifications table created successfully'); - - // 5. Проверяем, что таблицы созданы - const tablesResult = await client.query(` - SELECT table_name - FROM information_schema.tables - WHERE table_schema = 'public' - AND table_name IN ('notification_templates', 'notifications') - `); - - console.log('Created tables:'); - tablesResult.rows.forEach(row => { - console.log(`- ${row.table_name}`); - }); - - } finally { - client.release(); - await pool.end(); - } - - console.log('✅ Database initialization completed successfully'); - - } catch (error) { - console.error('❌ Database initialization error:', error); - } -} - -// Запускаем скрипт -initializeDatabase(); diff --git a/profile_views_patch.ts b/profile_views_patch.ts deleted file mode 100644 index f9abfb6..0000000 --- a/profile_views_patch.ts +++ /dev/null @@ -1,153 +0,0 @@ -// Патч для учета просмотренных профилей в функциональности бота - -// 1. Добавляем функцию recordProfileView в ProfileController -import { Profile, ProfileData } from '../models/Profile'; -import { ProfileService } from '../services/profileService'; - -export class ProfileController { - constructor(private profileService: ProfileService) {} - - // Существующие методы... - - // Новый метод для записи просмотра профиля - async recordProfileView(viewerTelegramId: string, viewedTelegramId: string, viewType: string = 'browse'): Promise { - try { - // Получаем внутренние ID пользователей - const viewerId = await this.profileService.getUserIdByTelegramId(viewerTelegramId); - const viewedId = await this.profileService.getUserIdByTelegramId(viewedTelegramId); - - if (!viewerId || !viewedId) { - console.error('Не удалось найти пользователей для записи просмотра профиля'); - return false; - } - - // Проверяем существование таблицы profile_views - const checkTableResult = await this.profileService.checkTableExists('profile_views'); - - if (checkTableResult) { - // Записываем просмотр - await this.profileService.recordProfileView(viewerId, viewedId, viewType); - console.log(`Просмотр профиля записан: ${viewerTelegramId} просмотрел ${viewedTelegramId}`); - return true; - } else { - console.log('Таблица profile_views не существует, просмотр не записан'); - return false; - } - } catch (error) { - console.error('Ошибка при записи просмотра профиля:', error); - return false; - } - } - - // Новый метод для получения списка просмотренных профилей - async getViewedProfiles(telegramId: string, limit: number = 50): Promise { - try { - // Получаем внутренний ID пользователя - const userId = await this.profileService.getUserIdByTelegramId(telegramId); - - if (!userId) { - console.error('Не удалось найти пользователя для получения списка просмотренных профилей'); - return []; - } - - // Проверяем существование таблицы profile_views - const checkTableResult = await this.profileService.checkTableExists('profile_views'); - - if (checkTableResult) { - // Получаем список просмотренных профилей - return await this.profileService.getViewedProfiles(userId, limit); - } else { - console.log('Таблица profile_views не существует, возвращаем пустой список'); - return []; - } - } catch (error) { - console.error('Ошибка при получении списка просмотренных профилей:', error); - return []; - } - } -} - -// 2. Добавляем функцию для проверки существования таблицы в ProfileService -async checkTableExists(tableName: string): Promise { - try { - const result = await query(` - SELECT EXISTS ( - SELECT FROM information_schema.tables - WHERE table_schema = 'public' - AND table_name = $1 - ); - `, [tableName]); - - return result.rows.length > 0 && result.rows[0].exists; - } catch (error) { - console.error(`Ошибка проверки существования таблицы ${tableName}:`, error); - return false; - } -} - -// 3. Обновляем обработчик показа профиля, чтобы записывать просмотры -async function handleShowProfile(ctx: any) { - // Существующий код... - - // После успешного отображения профиля записываем просмотр - const viewerTelegramId = ctx.from.id.toString(); - const viewedTelegramId = candidateProfile.telegram_id.toString(); - - try { - const profileController = new ProfileController(new ProfileService()); - await profileController.recordProfileView(viewerTelegramId, viewedTelegramId, 'browse'); - } catch (error) { - console.error('Ошибка при записи просмотра профиля:', error); - } - - // Остальной код... -} - -// 4. Обновляем функцию getNextCandidate, чтобы учитывать просмотренные профили -async function getNextCandidate(ctx: any) { - const telegramId = ctx.from.id.toString(); - const isNewUser = false; // Определяем, является ли пользователь новым - - try { - // Сначала пытаемся получить профили, которые пользователь еще не просматривал - const matchingService = new MatchingService(); - const profileService = new ProfileService(); - const profileController = new ProfileController(profileService); - - // Получаем UUID пользователя - const userId = await profileService.getUserIdByTelegramId(telegramId); - - if (!userId) { - console.error('Не удалось найти пользователя для получения следующего кандидата'); - return null; - } - - // Получаем список просмотренных профилей - const viewedProfiles = await profileController.getViewedProfiles(telegramId); - - // Получаем профиль пользователя - const userProfile = await profileService.getProfileByTelegramId(telegramId); - - if (!userProfile) { - console.error('Не удалось найти профиль пользователя для получения следующего кандидата'); - return null; - } - - // Ищем подходящий профиль с учетом просмотренных - const nextCandidate = await matchingService.getNextCandidate(telegramId, isNewUser); - - // Если найден кандидат, записываем просмотр - if (nextCandidate) { - const viewedTelegramId = await profileService.getTelegramIdByUserId(nextCandidate.userId); - - if (viewedTelegramId) { - await profileController.recordProfileView(telegramId, viewedTelegramId, 'browse'); - } - } - - return nextCandidate; - } catch (error) { - console.error('Ошибка при получении следующего кандидата:', error); - return null; - } -} diff --git a/scripts/README.md b/scripts/README.md index 6651500..687bed5 100644 --- a/scripts/README.md +++ b/scripts/README.md @@ -1,76 +1,49 @@ -# Исправление проблем с уведомлениями в боте +# Структура скриптов в директории `/scripts` -Этот набор скриптов предназначен для исправления проблем с обработкой уведомлений в боте. +Эта директория содержит вспомогательные скрипты для работы с Telegram Tinder Bot. -## Описание проблемы +## Основные скрипты -После внедрения системы уведомлений и связанных с ней изменений в базе данных, возникла проблема с обработкой callback запросов. Бот перестал реагировать на все callback запросы, кроме тех, что связаны с уведомлениями. +- `startup.sh` - Скрипт запуска бота в Docker-контейнере +- `migrate-sync.js` - Синхронизация миграций базы данных +- `createNotificationTables.js` - Создание таблиц для системы уведомлений +- `add-hobbies-column.js` - Добавление колонки интересов в профиль +- `create_profile_fix.js` - Исправление профилей пользователей +- `createProfileViewsTable.js` - Создание таблицы для учета просмотров профилей +- `update_bot_with_notifications.js` - Обновление бота с поддержкой уведомлений -Проблема вызвана следующими факторами: -1. Отсутствие или неверная структура таблиц в базе данных для хранения уведомлений -2. Отсутствие необходимых полей `state` и `state_data` в таблице `users` -3. Отсутствие правильной регистрации обработчиков уведомлений в файле `bot.ts` +## Директории -## Решение +- `/legacy` - Устаревшие и тестовые скрипты, сохраненные для истории -Для решения проблемы были созданы следующие скрипты: +## Использование скриптов -### 1. `fix_notification_callbacks.js` -Проверяет и создает необходимые таблицы и столбцы в базе данных: -- Таблицы `notifications`, `scheduled_notifications`, `notification_templates` -- Столбцы `notification_settings`, `state`, `state_data` в таблице `users` +Скрипты JavaScript можно запускать с помощью Node.js: -### 2. `update_bot_with_notifications.js` -Обновляет файл `bot.ts`: -- Добавляет импорт класса `NotificationHandlers` -- Добавляет объявление поля `notificationHandlers` в класс `TelegramTinderBot` -- Добавляет создание экземпляра `NotificationHandlers` в конструкторе -- Добавляет регистрацию обработчиков уведомлений в методе `registerHandlers` +```bash +node scripts/script-name.js +``` -### 3. `fix_all_notifications.js` -Запускает оба скрипта последовательно для полного исправления проблемы +Bash скрипты должны быть сделаны исполняемыми: -## Как использовать +```bash +chmod +x scripts/script-name.sh +./scripts/script-name.sh +``` -1. Остановите бота, если он запущен: - ```bash - # Нажмите Ctrl+C в терминале, где запущен бот - # или найдите процесс и завершите его - ``` +## Добавление новых скриптов -2. Запустите комплексный скрипт исправления: - ```bash - node scripts/fix_all_notifications.js - ``` +При добавлении новых скриптов соблюдайте следующие правила: +1. Используйте понятное имя файла, отражающее его назначение +2. Добавьте комментарии в начало файла с описанием его функциональности +3. Добавьте запись об этом скрипте в текущий файл README.md -3. После успешного выполнения скрипта перезапустите бота: - ```bash - npm run start - ``` +## Скрипты миграций -## Проверка результата +Миграции базы данных следует создавать с помощью команды: -После запуска бота убедитесь, что: -1. Бот отвечает на все callback запросы (включая кнопки, не связанные с уведомлениями) -2. Настройки уведомлений работают корректно (команда /notifications или кнопка в меню настроек) -3. Уведомления о лайках, супер-лайках и новых матчах приходят пользователям +```bash +npm run migrate:create your_migration_name +``` -## Если проблемы остались - -Если после выполнения всех шагов проблемы остались, выполните следующие проверки: - -1. Проверьте логи бота на наличие ошибок -2. Проверьте структуру базы данных: - ```sql - \dt -- Список всех таблиц - \d notifications -- Структура таблицы notifications - \d scheduled_notifications -- Структура таблицы scheduled_notifications - \d notification_templates -- Структура таблицы notification_templates - \d users -- Убедитесь, что поля state, state_data и notification_settings существуют - ``` - -3. Проверьте код в файлах: - - `src/bot.ts`: должен содержать импорт, создание и регистрацию `NotificationHandlers` - - `src/handlers/callbackHandlers.ts`: должен правильно обрабатывать все callback-запросы - -В случае обнаружения ошибок, исправьте их вручную и перезапустите бота. +Это создаст файл миграции в директории `/migrations`. diff --git a/scripts/checkCallbackHandlers.js b/scripts/legacy/checkCallbackHandlers.js similarity index 100% rename from scripts/checkCallbackHandlers.js rename to scripts/legacy/checkCallbackHandlers.js diff --git a/scripts/checkDatabase.js b/scripts/legacy/checkDatabase.js similarity index 100% rename from scripts/checkDatabase.js rename to scripts/legacy/checkDatabase.js diff --git a/scripts/checkProfileViews.js b/scripts/legacy/checkProfileViews.js similarity index 100% rename from scripts/checkProfileViews.js rename to scripts/legacy/checkProfileViews.js diff --git a/scripts/checkUserTable.js b/scripts/legacy/checkUserTable.js similarity index 100% rename from scripts/checkUserTable.js rename to scripts/legacy/checkUserTable.js diff --git a/scripts/cleanDatabase.js b/scripts/legacy/cleanDatabase.js similarity index 100% rename from scripts/cleanDatabase.js rename to scripts/legacy/cleanDatabase.js diff --git a/scripts/clearDatabase.js b/scripts/legacy/clearDatabase.js similarity index 100% rename from scripts/clearDatabase.js rename to scripts/legacy/clearDatabase.js diff --git a/scripts/clearDatabase.mjs b/scripts/legacy/clearDatabase.mjs similarity index 100% rename from scripts/clearDatabase.mjs rename to scripts/legacy/clearDatabase.mjs diff --git a/scripts/clear_database.sql b/scripts/legacy/clear_database.sql similarity index 100% rename from scripts/clear_database.sql rename to scripts/legacy/clear_database.sql diff --git a/scripts/createProfileViewsTable.ts b/scripts/legacy/createProfileViewsTable.ts similarity index 100% rename from scripts/createProfileViewsTable.ts rename to scripts/legacy/createProfileViewsTable.ts diff --git a/scripts/fixCallbackHandlers.js b/scripts/legacy/fixCallbackHandlers.js similarity index 100% rename from scripts/fixCallbackHandlers.js rename to scripts/legacy/fixCallbackHandlers.js diff --git a/scripts/fixDatabaseStructure.js b/scripts/legacy/fixDatabaseStructure.js similarity index 100% rename from scripts/fixDatabaseStructure.js rename to scripts/legacy/fixDatabaseStructure.js diff --git a/scripts/fix_all_notifications.js b/scripts/legacy/fix_all_notifications.js similarity index 100% rename from scripts/fix_all_notifications.js rename to scripts/legacy/fix_all_notifications.js diff --git a/scripts/fix_notification_callbacks.js b/scripts/legacy/fix_notification_callbacks.js similarity index 100% rename from scripts/fix_notification_callbacks.js rename to scripts/legacy/fix_notification_callbacks.js diff --git a/scripts/testCallbacks.js b/scripts/legacy/testCallbacks.js similarity index 100% rename from scripts/testCallbacks.js rename to scripts/legacy/testCallbacks.js diff --git a/scripts/testMatching.js b/scripts/legacy/testMatching.js similarity index 100% rename from scripts/testMatching.js rename to scripts/legacy/testMatching.js diff --git a/scripts/testProfileViews.js b/scripts/legacy/testProfileViews.js similarity index 100% rename from scripts/testProfileViews.js rename to scripts/legacy/testProfileViews.js diff --git a/scripts/testVipMethod.js b/scripts/legacy/testVipMethod.js similarity index 100% rename from scripts/testVipMethod.js rename to scripts/legacy/testVipMethod.js diff --git a/scripts/testVipStatus.js b/scripts/legacy/testVipStatus.js similarity index 100% rename from scripts/testVipStatus.js rename to scripts/legacy/testVipStatus.js diff --git a/scripts/startup.sh b/scripts/startup.sh new file mode 100644 index 0000000..9cb83ea --- /dev/null +++ b/scripts/startup.sh @@ -0,0 +1,16 @@ +#!/bin/sh +# startup.sh - Script to run migrations and start the bot + +echo "🚀 Starting Telegram Tinder Bot..." + +# Wait for database to be ready +echo "⏳ Waiting for database to be ready..." +sleep 5 + +# Run database migrations +echo "🔄 Running database migrations..." +node dist/database/migrateOnStartup.js + +# Start the bot +echo "✅ Starting the bot..." +node dist/bot.js diff --git a/set-premium.js b/set-premium.js deleted file mode 100644 index 1cc5808..0000000 Binary files a/set-premium.js and /dev/null differ diff --git a/sql/consolidated.sql b/sql/consolidated.sql new file mode 100644 index 0000000..a61b524 --- /dev/null +++ b/sql/consolidated.sql @@ -0,0 +1,404 @@ +# Consolidated SQL файл для миграции базы данных Telegram Tinder Bot +# Этот файл содержит все необходимые SQL-запросы для создания базы данных с нуля + +-- Создание расширения для UUID +CREATE EXTENSION IF NOT EXISTS "uuid-ossp"; + +-- Создание перечислений +CREATE TYPE gender_type AS ENUM ('male', 'female', 'other'); +CREATE TYPE swipe_action AS ENUM ('like', 'dislike', 'superlike'); + +-- Создание таблицы пользователей +CREATE TABLE IF NOT EXISTS users ( + id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), + telegram_id BIGINT UNIQUE NOT NULL, + username VARCHAR(255), + first_name VARCHAR(255) NOT NULL, + last_name VARCHAR(255), + language_code VARCHAR(10), + is_premium BOOLEAN DEFAULT FALSE, + is_active BOOLEAN DEFAULT TRUE, + created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP +); + +-- Создание таблицы профилей +CREATE TABLE IF NOT EXISTS profiles ( + id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), + user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE, + name VARCHAR(255) NOT NULL, + age INTEGER NOT NULL CHECK (age >= 18), + gender gender_type NOT NULL, + bio TEXT, + photos TEXT[], -- JSON array of photo file_ids + location VARCHAR(255), + job VARCHAR(255), + interests TEXT[], -- JSON array of interests + last_active TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP, + is_completed BOOLEAN DEFAULT FALSE, + created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP, + CONSTRAINT unique_user_profile UNIQUE (user_id) +); + +-- Создание индекса для поиска по возрасту и полу +CREATE INDEX idx_profiles_age_gender ON profiles(age, gender); + +-- Создание таблицы предпочтений поиска +CREATE TABLE IF NOT EXISTS search_preferences ( + id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), + user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE, + age_min INTEGER NOT NULL DEFAULT 18 CHECK (age_min >= 18), + age_max INTEGER NOT NULL DEFAULT 99 CHECK (age_max >= age_min), + looking_for gender_type NOT NULL, + distance_max INTEGER, -- max distance in km, NULL means no limit + created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP, + CONSTRAINT unique_user_preferences UNIQUE (user_id) +); + +-- Создание таблицы действий (лайки/дизлайки) +CREATE TABLE IF NOT EXISTS swipes ( + id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), + user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE, + target_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE, + action swipe_action NOT NULL, + created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP, + CONSTRAINT unique_swipe UNIQUE (user_id, target_id) +); + +-- Создание индекса для быстрого поиска матчей +CREATE INDEX idx_swipes_user_target ON swipes(user_id, target_id); + +-- Создание таблицы матчей +CREATE TABLE IF NOT EXISTS matches ( + id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), + user_id_1 UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE, + user_id_2 UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE, + created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP, + is_active BOOLEAN DEFAULT TRUE, + CONSTRAINT unique_match UNIQUE (user_id_1, user_id_2) +); + +-- Создание индекса для быстрого поиска матчей по пользователю +CREATE INDEX idx_matches_user_id_1 ON matches(user_id_1); +CREATE INDEX idx_matches_user_id_2 ON matches(user_id_2); + +-- Создание таблицы блокировок +CREATE TABLE IF NOT EXISTS blocks ( + id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), + blocker_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE, + blocked_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE, + created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP, + CONSTRAINT unique_block UNIQUE (blocker_id, blocked_id) +); + +-- Создание таблицы сообщений +CREATE TABLE IF NOT EXISTS messages ( + id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), + match_id UUID NOT NULL REFERENCES matches(id) ON DELETE CASCADE, + sender_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE, + text TEXT NOT NULL, + is_read BOOLEAN DEFAULT FALSE, + created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP +); + +-- Создание индекса для быстрого поиска сообщений +CREATE INDEX idx_messages_match_id ON messages(match_id); + +-- Создание таблицы уведомлений +CREATE TABLE IF NOT EXISTS notifications ( + id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), + user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE, + type VARCHAR(50) NOT NULL, -- new_match, new_message, etc. + content TEXT NOT NULL, + is_read BOOLEAN DEFAULT FALSE, + reference_id UUID, -- Can reference a match_id, message_id, etc. + created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP +); + +-- Создание индекса для быстрого поиска уведомлений +CREATE INDEX idx_notifications_user_id ON notifications(user_id); + +-- Создание таблицы настроек +CREATE TABLE IF NOT EXISTS settings ( + id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), + user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE, + notifications_enabled BOOLEAN DEFAULT TRUE, + show_online_status BOOLEAN DEFAULT TRUE, + visibility BOOLEAN DEFAULT TRUE, -- whether profile is visible in search + theme VARCHAR(20) DEFAULT 'light', + created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP, + CONSTRAINT unique_user_settings UNIQUE (user_id) +); + +-- Создание таблицы просмотров профиля +CREATE TABLE IF NOT EXISTS profile_views ( + id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), + viewer_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE, + viewed_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE, + view_count INTEGER DEFAULT 1, + last_viewed_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP, + created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP, + CONSTRAINT unique_view UNIQUE (viewer_id, viewed_id) +); + +-- Создание таблицы для премиум-пользователей +CREATE TABLE IF NOT EXISTS premium_features ( + id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), + user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE, + is_premium BOOLEAN DEFAULT FALSE, + superlike_quota INTEGER DEFAULT 1, + spotlight_quota INTEGER DEFAULT 0, + see_likes BOOLEAN DEFAULT FALSE, -- Can see who liked their profile + unlimited_likes BOOLEAN DEFAULT FALSE, + expires_at TIMESTAMP WITH TIME ZONE, + created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP, + CONSTRAINT unique_user_premium UNIQUE (user_id) +); + +-- Функция для обновления поля updated_at +CREATE OR REPLACE FUNCTION update_updated_at() +RETURNS TRIGGER AS $$ +BEGIN + NEW.updated_at = CURRENT_TIMESTAMP; + RETURN NEW; +END; +$$ LANGUAGE plpgsql; + +-- Триггеры для обновления поля updated_at +CREATE TRIGGER update_users_updated_at +BEFORE UPDATE ON users +FOR EACH ROW +EXECUTE FUNCTION update_updated_at(); + +CREATE TRIGGER update_profiles_updated_at +BEFORE UPDATE ON profiles +FOR EACH ROW +EXECUTE FUNCTION update_updated_at(); + +CREATE TRIGGER update_search_preferences_updated_at +BEFORE UPDATE ON search_preferences +FOR EACH ROW +EXECUTE FUNCTION update_updated_at(); + +CREATE TRIGGER update_settings_updated_at +BEFORE UPDATE ON settings +FOR EACH ROW +EXECUTE FUNCTION update_updated_at(); + +CREATE TRIGGER update_premium_features_updated_at +BEFORE UPDATE ON premium_features +FOR EACH ROW +EXECUTE FUNCTION update_updated_at(); + +-- Индекс для поиска пользователей по Telegram ID (часто используемый запрос) +CREATE INDEX IF NOT EXISTS idx_users_telegram_id ON users(telegram_id); + +-- Индекс для статуса профиля (активный/неактивный, завершенный/незавершенный) +CREATE INDEX IF NOT EXISTS idx_profiles_is_completed ON profiles(is_completed); + +-- Представление для статистики +CREATE OR REPLACE VIEW user_statistics AS +SELECT + u.id, + u.telegram_id, + (SELECT COUNT(*) FROM swipes WHERE user_id = u.id AND action = 'like') AS likes_given, + (SELECT COUNT(*) FROM swipes WHERE user_id = u.id AND action = 'dislike') AS dislikes_given, + (SELECT COUNT(*) FROM swipes WHERE target_id = u.id AND action = 'like') AS likes_received, + (SELECT COUNT(*) FROM matches WHERE user_id_1 = u.id OR user_id_2 = u.id) AS matches_count, + (SELECT COUNT(*) FROM messages WHERE sender_id = u.id) AS messages_sent, + (SELECT COUNT(*) FROM profile_views WHERE viewed_id = u.id) AS profile_views +FROM users u; + +-- Функция для создания матча при взаимных лайках +CREATE OR REPLACE FUNCTION create_match_on_mutual_like() +RETURNS TRIGGER AS $$ +DECLARE + reverse_like_exists BOOLEAN; +BEGIN + -- Check if there is a reverse like + SELECT EXISTS ( + SELECT 1 + FROM swipes + WHERE user_id = NEW.target_id + AND target_id = NEW.user_id + AND action = 'like' + ) INTO reverse_like_exists; + + -- If there is a reverse like, create a match + IF reverse_like_exists AND NEW.action = 'like' THEN + INSERT INTO matches (user_id_1, user_id_2) + VALUES ( + LEAST(NEW.user_id, NEW.target_id), + GREATEST(NEW.user_id, NEW.target_id) + ) + ON CONFLICT (user_id_1, user_id_2) DO NOTHING; + + -- Create notifications for both users + INSERT INTO notifications (user_id, type, content, reference_id) + VALUES ( + NEW.user_id, + 'new_match', + 'У вас новый матч!', + (SELECT id FROM matches WHERE + (user_id_1 = LEAST(NEW.user_id, NEW.target_id) AND user_id_2 = GREATEST(NEW.user_id, NEW.target_id)) + ) + ); + + INSERT INTO notifications (user_id, type, content, reference_id) + VALUES ( + NEW.target_id, + 'new_match', + 'У вас новый матч!', + (SELECT id FROM matches WHERE + (user_id_1 = LEAST(NEW.user_id, NEW.target_id) AND user_id_2 = GREATEST(NEW.user_id, NEW.target_id)) + ) + ); + END IF; + + RETURN NEW; +END; +$$ LANGUAGE plpgsql; + +-- Триггер для создания матча при взаимных лайках +CREATE TRIGGER create_match_trigger +AFTER INSERT ON swipes +FOR EACH ROW +EXECUTE FUNCTION create_match_on_mutual_like(); + +-- Функция для создания уведомления о новом сообщении +CREATE OR REPLACE FUNCTION notify_new_message() +RETURNS TRIGGER AS $$ +DECLARE + recipient_id UUID; + match_record RECORD; +BEGIN + -- Get the match record + SELECT * INTO match_record FROM matches WHERE id = NEW.match_id; + + -- Determine the recipient + IF match_record.user_id_1 = NEW.sender_id THEN + recipient_id := match_record.user_id_2; + ELSE + recipient_id := match_record.user_id_1; + END IF; + + -- Create notification + INSERT INTO notifications (user_id, type, content, reference_id) + VALUES ( + recipient_id, + 'new_message', + 'У вас новое сообщение!', + NEW.id + ); + + RETURN NEW; +END; +$$ LANGUAGE plpgsql; + +-- Триггер для создания уведомления о новом сообщении +CREATE TRIGGER notify_new_message_trigger +AFTER INSERT ON messages +FOR EACH ROW +EXECUTE FUNCTION notify_new_message(); + +-- Функция для обновления времени последней активности пользователя +CREATE OR REPLACE FUNCTION update_last_active() +RETURNS TRIGGER AS $$ +BEGIN + UPDATE profiles + SET last_active = CURRENT_TIMESTAMP + WHERE user_id = NEW.user_id; + + RETURN NEW; +END; +$$ LANGUAGE plpgsql; + +-- Триггер для обновления времени последней активности при свайпах +CREATE TRIGGER update_last_active_on_swipe +AFTER INSERT ON swipes +FOR EACH ROW +EXECUTE FUNCTION update_last_active(); + +-- Триггер для обновления времени последней активности при отправке сообщений +CREATE TRIGGER update_last_active_on_message +AFTER INSERT ON messages +FOR EACH ROW +EXECUTE FUNCTION update_last_active(); + +-- Создание функции для автоматического создания профиля при создании пользователя +CREATE OR REPLACE FUNCTION create_initial_profile() +RETURNS TRIGGER AS $$ +BEGIN + INSERT INTO profiles (user_id, name, age, gender) + VALUES (NEW.id, COALESCE(NEW.first_name, 'User'), 18, 'other'); + + RETURN NEW; +END; +$$ LANGUAGE plpgsql; + +-- Триггер для автоматического создания профиля при создании пользователя +CREATE TRIGGER create_profile_trigger +AFTER INSERT ON users +FOR EACH ROW +EXECUTE FUNCTION create_initial_profile(); + +-- Индексы для оптимизации частых запросов +CREATE INDEX IF NOT EXISTS idx_profiles_last_active ON profiles(last_active); +CREATE INDEX IF NOT EXISTS idx_swipes_action ON swipes(action); +CREATE INDEX IF NOT EXISTS idx_notifications_is_read ON notifications(is_read); +CREATE INDEX IF NOT EXISTS idx_messages_is_read ON messages(is_read); + +-- Добавление ограничений для проверки возраста +ALTER TABLE profiles DROP CONSTRAINT IF EXISTS age_check; +ALTER TABLE profiles ADD CONSTRAINT age_check CHECK (age >= 18 AND age <= 99); + +-- Добавление ограничений для предпочтений +ALTER TABLE search_preferences DROP CONSTRAINT IF EXISTS age_range_check; +ALTER TABLE search_preferences ADD CONSTRAINT age_range_check CHECK (age_min >= 18 AND age_max >= age_min AND age_max <= 99); + +-- Комментарии к таблицам для документации +COMMENT ON TABLE users IS 'Таблица пользователей Telegram'; +COMMENT ON TABLE profiles IS 'Профили пользователей для знакомств'; +COMMENT ON TABLE search_preferences IS 'Предпочтения поиска пользователей'; +COMMENT ON TABLE swipes IS 'История лайков/дислайков'; +COMMENT ON TABLE matches IS 'Совпадения (матчи) между пользователями'; +COMMENT ON TABLE messages IS 'Сообщения между пользователями'; +COMMENT ON TABLE notifications IS 'Уведомления для пользователей'; +COMMENT ON TABLE settings IS 'Настройки пользователей'; +COMMENT ON TABLE profile_views IS 'История просмотров профилей'; +COMMENT ON TABLE premium_features IS 'Премиум-функции для пользователей'; + +-- Представление для быстрого получения активных матчей с информацией о пользователе +CREATE OR REPLACE VIEW active_matches AS +SELECT + m.id AS match_id, + m.created_at AS match_date, + CASE + WHEN m.user_id_1 = u1.id THEN u2.id + ELSE u1.id + END AS partner_id, + CASE + WHEN m.user_id_1 = u1.id THEN u2.telegram_id + ELSE u1.telegram_id + END AS partner_telegram_id, + CASE + WHEN m.user_id_1 = u1.id THEN p2.name + ELSE p1.name + END AS partner_name, + CASE + WHEN m.user_id_1 = u1.id THEN p2.photos[1] + ELSE p1.photos[1] + END AS partner_photo, + (SELECT COUNT(*) FROM messages WHERE match_id = m.id) AS message_count, + (SELECT COUNT(*) FROM messages WHERE match_id = m.id AND is_read = false AND sender_id != u1.id) AS unread_count, + m.is_active +FROM matches m +JOIN users u1 ON m.user_id_1 = u1.id +JOIN users u2 ON m.user_id_2 = u2.id +JOIN profiles p1 ON u1.id = p1.user_id +JOIN profiles p2 ON u2.id = p2.user_id +WHERE m.is_active = true; diff --git a/src/database/migrateOnStartup.ts b/src/database/migrateOnStartup.ts new file mode 100644 index 0000000..55c58d3 --- /dev/null +++ b/src/database/migrateOnStartup.ts @@ -0,0 +1,108 @@ +// Script to run migrations on startup +import { Pool } from 'pg'; +import * as fs from 'fs'; +import * as path from 'path'; +import 'dotenv/config'; + +async function runMigrations() { + console.log('Starting database migration...'); + + // Create a connection pool + const pool = new Pool({ + host: process.env.DB_HOST || 'localhost', + port: parseInt(process.env.DB_PORT || '5432'), + database: process.env.DB_NAME || 'telegram_tinder_bot', + user: process.env.DB_USERNAME || 'postgres', + password: process.env.DB_PASSWORD, + max: 20, + idleTimeoutMillis: 30000, + connectionTimeoutMillis: 2000, + }); + + try { + // Test connection + const testRes = await pool.query('SELECT NOW()'); + console.log(`Database connection successful at ${testRes.rows[0].now}`); + + // Create migrations table if not exists + await pool.query(` + CREATE TABLE IF NOT EXISTS migrations ( + id SERIAL PRIMARY KEY, + name VARCHAR(255) NOT NULL, + executed_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP + ) + `); + + // Get list of executed migrations + const migrationRes = await pool.query('SELECT name FROM migrations'); + const executedMigrations = migrationRes.rows.map(row => row.name); + console.log(`Found ${executedMigrations.length} executed migrations`); + + // Get migration files + const migrationsPath = path.join(__dirname, 'migrations'); + let migrationFiles = []; + + try { + migrationFiles = fs.readdirSync(migrationsPath) + .filter(file => file.endsWith('.sql')) + .sort(); + console.log(`Found ${migrationFiles.length} migration files`); + } catch (error: any) { + console.error(`Error reading migrations directory: ${error.message}`); + console.log('Continuing with built-in consolidated migration...'); + + // If no external files found, use consolidated.sql + const consolidatedSQL = fs.readFileSync(path.join(__dirname, 'migrations', 'consolidated.sql'), 'utf8'); + + console.log('Executing consolidated migration...'); + await pool.query(consolidatedSQL); + + if (!executedMigrations.includes('consolidated.sql')) { + await pool.query( + 'INSERT INTO migrations (name) VALUES ($1)', + ['consolidated.sql'] + ); + } + + console.log('Consolidated migration completed successfully'); + return; + } + + // Run each migration that hasn't been executed yet + for (const file of migrationFiles) { + if (!executedMigrations.includes(file)) { + console.log(`Executing migration: ${file}`); + const sql = fs.readFileSync(path.join(migrationsPath, file), 'utf8'); + + try { + await pool.query('BEGIN'); + await pool.query(sql); + await pool.query( + 'INSERT INTO migrations (name) VALUES ($1)', + [file] + ); + await pool.query('COMMIT'); + console.log(`Migration ${file} completed successfully`); + } catch (error: any) { + await pool.query('ROLLBACK'); + console.error(`Error executing migration ${file}: ${error.message}`); + throw error; + } + } else { + console.log(`Migration ${file} already executed, skipping`); + } + } + + console.log('All migrations completed successfully!'); + } catch (error: any) { + console.error(`Migration failed: ${error.message}`); + process.exit(1); + } finally { + await pool.end(); + } +} + +runMigrations().catch((error: any) => { + console.error('Unhandled error during migration:', error); + process.exit(1); +}); diff --git a/src/database/migrations/consolidated.sql b/src/database/migrations/consolidated.sql new file mode 100644 index 0000000..2c24dff --- /dev/null +++ b/src/database/migrations/consolidated.sql @@ -0,0 +1,182 @@ +-- Consolidated migrations for Telegram Tinder Bot + +-- Create extension for UUID if not exists +CREATE EXTENSION IF NOT EXISTS "uuid-ossp"; +CREATE EXTENSION IF NOT EXISTS "pgcrypto"; + +---------------------------------------------- +-- Core Tables +---------------------------------------------- + +-- Users table +CREATE TABLE IF NOT EXISTS users ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + telegram_id BIGINT UNIQUE NOT NULL, + username VARCHAR(255), + first_name VARCHAR(255), + last_name VARCHAR(255), + language_code VARCHAR(10) DEFAULT 'ru', + is_active BOOLEAN DEFAULT TRUE, + created_at TIMESTAMP DEFAULT NOW(), + last_active_at TIMESTAMP DEFAULT NOW(), + premium BOOLEAN DEFAULT FALSE, + state VARCHAR(255), + state_data JSONB DEFAULT '{}'::jsonb +); + +-- Profiles table +CREATE TABLE IF NOT EXISTS profiles ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + user_id UUID REFERENCES users(id) ON DELETE CASCADE, + name VARCHAR(255) NOT NULL, + age INTEGER NOT NULL CHECK (age >= 18 AND age <= 100), + gender VARCHAR(10) NOT NULL CHECK (gender IN ('male', 'female', 'other')), + interested_in VARCHAR(10) NOT NULL CHECK (interested_in IN ('male', 'female', 'both')), + bio TEXT, + photos JSONB DEFAULT '[]', + interests JSONB DEFAULT '[]', + city VARCHAR(255), + education VARCHAR(255), + job VARCHAR(255), + height INTEGER, + location_lat DECIMAL(10, 8), + location_lon DECIMAL(11, 8), + search_min_age INTEGER DEFAULT 18, + search_max_age INTEGER DEFAULT 50, + search_max_distance INTEGER DEFAULT 50, + is_verified BOOLEAN DEFAULT FALSE, + is_visible BOOLEAN DEFAULT TRUE, + created_at TIMESTAMP DEFAULT NOW(), + updated_at TIMESTAMP DEFAULT NOW(), + religion VARCHAR(255), + dating_goal VARCHAR(50), + smoking VARCHAR(20), + drinking VARCHAR(20), + has_kids BOOLEAN DEFAULT FALSE +); + +-- Swipes table +CREATE TABLE IF NOT EXISTS swipes ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + user_id UUID REFERENCES users(id) ON DELETE CASCADE, + target_user_id UUID REFERENCES users(id) ON DELETE CASCADE, + type VARCHAR(20) NOT NULL CHECK (type IN ('like', 'pass', 'superlike')), + created_at TIMESTAMP DEFAULT NOW(), + is_match BOOLEAN DEFAULT FALSE, + UNIQUE(user_id, target_user_id) +); + +-- Matches table +CREATE TABLE IF NOT EXISTS matches ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + user_id_1 UUID REFERENCES users(id) ON DELETE CASCADE, + user_id_2 UUID REFERENCES users(id) ON DELETE CASCADE, + created_at TIMESTAMP DEFAULT NOW(), + last_message_at TIMESTAMP, + is_active BOOLEAN DEFAULT TRUE, + is_super_match BOOLEAN DEFAULT FALSE, + unread_count_1 INTEGER DEFAULT 0, + unread_count_2 INTEGER DEFAULT 0, + UNIQUE(user_id_1, user_id_2) +); + +-- Messages table +CREATE TABLE IF NOT EXISTS messages ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + match_id UUID REFERENCES matches(id) ON DELETE CASCADE, + sender_id UUID REFERENCES users(id) ON DELETE CASCADE, + receiver_id UUID REFERENCES users(id) ON DELETE CASCADE, + content TEXT NOT NULL, + message_type VARCHAR(20) DEFAULT 'text' CHECK (message_type IN ('text', 'photo', 'gif', 'sticker')), + created_at TIMESTAMP DEFAULT NOW(), + is_read BOOLEAN DEFAULT FALSE +); + +---------------------------------------------- +-- Profile Views Table +---------------------------------------------- + +-- Table for tracking profile views +CREATE TABLE IF NOT EXISTS profile_views ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + viewer_id UUID REFERENCES users(id) ON DELETE CASCADE, + viewed_id UUID REFERENCES users(id) ON DELETE CASCADE, + view_type VARCHAR(20) DEFAULT 'browse' CHECK (view_type IN ('browse', 'search', 'recommended')), + viewed_at TIMESTAMP DEFAULT NOW(), + CONSTRAINT unique_profile_view UNIQUE (viewer_id, viewed_id, view_type) +); + +-- Index for profile views +CREATE INDEX IF NOT EXISTS idx_profile_views_viewer ON profile_views(viewer_id); +CREATE INDEX IF NOT EXISTS idx_profile_views_viewed ON profile_views(viewed_id); + +---------------------------------------------- +-- Notification Tables +---------------------------------------------- + +-- Notifications table +CREATE TABLE IF NOT EXISTS notifications ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE, + type VARCHAR(50) NOT NULL, + content JSONB NOT NULL DEFAULT '{}', + is_read BOOLEAN DEFAULT FALSE, + processed BOOLEAN DEFAULT FALSE, + created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW() +); + +-- Notification settings table +CREATE TABLE IF NOT EXISTS notification_settings ( + user_id UUID PRIMARY KEY REFERENCES users(id) ON DELETE CASCADE, + new_matches BOOLEAN DEFAULT TRUE, + new_messages BOOLEAN DEFAULT TRUE, + new_likes BOOLEAN DEFAULT TRUE, + reminders BOOLEAN DEFAULT TRUE, + daily_summary BOOLEAN DEFAULT FALSE, + time_preference VARCHAR(20) DEFAULT 'evening', + do_not_disturb BOOLEAN DEFAULT FALSE, + do_not_disturb_start TIME, + do_not_disturb_end TIME, + updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW() +); + +-- Scheduled notifications table +CREATE TABLE IF NOT EXISTS scheduled_notifications ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE, + type VARCHAR(50) NOT NULL, + content JSONB NOT NULL DEFAULT '{}', + scheduled_at TIMESTAMP WITH TIME ZONE NOT NULL, + processed BOOLEAN DEFAULT FALSE, + created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW() +); + +---------------------------------------------- +-- Indexes for better performance +---------------------------------------------- + +-- User Indexes +CREATE INDEX IF NOT EXISTS idx_users_telegram_id ON users(telegram_id); + +-- Profile Indexes +CREATE INDEX IF NOT EXISTS idx_profiles_user_id ON profiles(user_id); +CREATE INDEX IF NOT EXISTS idx_profiles_location ON profiles(location_lat, location_lon) + WHERE location_lat IS NOT NULL AND location_lon IS NOT NULL; +CREATE INDEX IF NOT EXISTS idx_profiles_age_gender ON profiles(age, gender, interested_in); + +-- Swipe Indexes +CREATE INDEX IF NOT EXISTS idx_swipes_user ON swipes(user_id, target_user_id); + +-- Match Indexes +CREATE INDEX IF NOT EXISTS idx_matches_users ON matches(user_id_1, user_id_2); + +-- Message Indexes +CREATE INDEX IF NOT EXISTS idx_messages_match ON messages(match_id, created_at); + +-- Notification Indexes +CREATE INDEX IF NOT EXISTS idx_notifications_user_id ON notifications(user_id); +CREATE INDEX IF NOT EXISTS idx_notifications_type ON notifications(type); +CREATE INDEX IF NOT EXISTS idx_notifications_created_at ON notifications(created_at); +CREATE INDEX IF NOT EXISTS idx_scheduled_notifications_user_id ON scheduled_notifications(user_id); +CREATE INDEX IF NOT EXISTS idx_scheduled_notifications_scheduled_at ON scheduled_notifications(scheduled_at); +CREATE INDEX IF NOT EXISTS idx_scheduled_notifications_processed ON scheduled_notifications(processed); diff --git a/src/premium/README.md b/src/premium/README.md new file mode 100644 index 0000000..aa34eef --- /dev/null +++ b/src/premium/README.md @@ -0,0 +1,62 @@ +# Модуль премиум-функций Telegram Tinder Bot + +Этот каталог содержит модули и скрипты для управления премиум-функциями бота. + +## Содержимое + +- `add-premium-columns.js` - Добавление колонок для премиум-функций в базу данных (версия JavaScript) +- `add-premium-columns.ts` - Добавление колонок для премиум-функций в базу данных (версия TypeScript) +- `add-premium-columns-direct.js` - Прямое добавление премиум-колонок без миграций +- `addPremiumColumn.js` - Добавление отдельной колонки премиум в таблицу пользователей +- `setPremiumStatus.js` - Обновление статуса премиум для пользователей + +## Премиум-функции + +В боте реализованы следующие премиум-функции: + +1. **Неограниченные лайки** - снятие дневного лимита на количество лайков +2. **Супер-лайки** - возможность отправлять супер-лайки (повышенный приоритет) +3. **Просмотр лайков** - возможность видеть, кто поставил лайк вашему профилю +4. **Скрытый режим** - возможность скрывать свою активность +5. **Расширенные фильтры** - дополнительные параметры для поиска + +## Использование + +### Добавление премиум-колонок в базу данных + +```bash +node src/premium/add-premium-columns.js +``` + +### Изменение премиум-статуса пользователя + +```typescript +import { PremiumService } from '../services/premiumService'; + +// Установка премиум-статуса для пользователя +const premiumService = new PremiumService(); +await premiumService.setPremiumStatus(userId, true, 30); // 30 дней премиума +``` + +## Интеграция в основной код + +Проверка премиум-статуса должна выполняться следующим образом: + +```typescript +// В классах контроллеров +const isPremium = await this.premiumService.checkUserPremium(userId); + +if (isPremium) { + // Предоставить премиум-функцию +} else { + // Сообщить о необходимости премиум-подписки +} +``` + +## Период действия премиум-статуса + +По умолчанию премиум-статус устанавливается на 30 дней. Для изменения срока используйте третий параметр в методе `setPremiumStatus`. + +## Дополнительная информация + +Более подробная информация о премиум-функциях содержится в документации проекта в каталоге `docs/VIP_FUNCTIONS.md`. diff --git a/scripts/add-premium-columns-direct.js b/src/premium/add-premium-columns-direct.js similarity index 100% rename from scripts/add-premium-columns-direct.js rename to src/premium/add-premium-columns-direct.js diff --git a/scripts/add-premium-columns.js b/src/premium/add-premium-columns.js similarity index 100% rename from scripts/add-premium-columns.js rename to src/premium/add-premium-columns.js diff --git a/scripts/add-premium-columns.ts b/src/premium/add-premium-columns.ts similarity index 100% rename from scripts/add-premium-columns.ts rename to src/premium/add-premium-columns.ts diff --git a/scripts/addPremiumColumn.js b/src/premium/addPremiumColumn.js similarity index 100% rename from scripts/addPremiumColumn.js rename to src/premium/addPremiumColumn.js diff --git a/scripts/setPremiumStatus.js b/src/premium/setPremiumStatus.js similarity index 100% rename from scripts/setPremiumStatus.js rename to src/premium/setPremiumStatus.js diff --git a/start.bat b/start.bat new file mode 100644 index 0000000..6ef2b25 --- /dev/null +++ b/start.bat @@ -0,0 +1,241 @@ +@echo off +:: start.bat - Скрипт для запуска Telegram Tinder Bot на Windows +:: Позволяет выбрать между локальной БД в контейнере или внешней БД + +echo ================================================ +echo Запуск Telegram Tinder Bot +echo ================================================ + +:: Проверка наличия Docker и Docker Compose +WHERE docker >nul 2>&1 +IF %ERRORLEVEL% NEQ 0 ( + echo [31mОШИБКА: Docker не установлен![0m + echo Установите Docker Desktop для Windows: https://docs.docker.com/desktop/install/windows-install/ + exit /b 1 +) + +:: Проверяем наличие .env файла +IF NOT EXIST .env ( + echo [33mФайл .env не найден. Создаем из шаблона...[0m + IF EXIST .env.example ( + copy .env.example .env + echo [32mФайл .env создан из шаблона. Пожалуйста, отредактируйте его с вашими настройками.[0m + ) ELSE ( + echo [31mОШИБКА: Файл .env.example не найден. Создайте файл .env вручную.[0m + exit /b 1 + ) +) + +:: Спрашиваем про запуск базы данных +set /p use_container_db="Запустить базу данных PostgreSQL в контейнере? (y/n): " + +:: Функции для работы с docker-compose +IF /I "%use_container_db%" NEQ "y" ( + :: Запрашиваем параметры подключения к внешней БД + echo [36mВведите параметры подключения к внешней базе данных:[0m + set /p db_host="Хост (например, localhost): " + set /p db_port="Порт (например, 5432): " + set /p db_name="Имя базы данных: " + set /p db_user="Имя пользователя: " + set /p db_password="Пароль: " + + :: Модифицируем docker-compose.yml + echo [33mМодифицируем docker-compose.yml для работы с внешней базой данных...[0m + + :: Сохраняем оригинальную версию файла + copy docker-compose.yml docker-compose.yml.bak + + :: Создаем временный файл с модифицированным содержимым + ( + echo version: '3.8' + echo. + echo services: + echo bot: + echo build: . + echo container_name: telegram-tinder-bot + echo restart: unless-stopped + echo env_file: .env + echo environment: + echo - NODE_ENV=production + echo - DB_HOST=%db_host% + echo - DB_PORT=%db_port% + echo - DB_NAME=%db_name% + echo - DB_USERNAME=%db_user% + echo - DB_PASSWORD=%db_password% + echo volumes: + echo - ./uploads:/app/uploads + echo - ./logs:/app/logs + echo networks: + echo - bot-network + echo healthcheck: + echo test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://localhost:3000/health"] + echo interval: 30s + echo timeout: 5s + echo retries: 3 + echo start_period: 10s + echo. + echo adminer: + echo image: adminer:latest + echo container_name: adminer-tinder + echo restart: unless-stopped + echo ports: + echo - "8080:8080" + echo networks: + echo - bot-network + echo. + echo volumes: + echo postgres_data: + echo. + echo networks: + echo bot-network: + echo driver: bridge + ) > docker-compose.temp.yml + + :: Заменяем оригинальный файл + move /y docker-compose.temp.yml docker-compose.yml + + echo [32mdocker-compose.yml обновлен для работы с внешней базой данных[0m + + :: Обновляем .env файл + echo [33mОбновляем файл .env с параметрами внешней базы данных...[0m + + :: Создаем временный файл + type NUL > .env.temp + + :: Читаем .env построчно и заменяем нужные строки + for /f "tokens=*" %%a in (.env) do ( + set line=%%a + set line=!line:DB_HOST=*! + if "!line:~0,1!" == "*" ( + echo DB_HOST=%db_host%>> .env.temp + ) else ( + set line=!line:DB_PORT=*! + if "!line:~0,1!" == "*" ( + echo DB_PORT=%db_port%>> .env.temp + ) else ( + set line=!line:DB_NAME=*! + if "!line:~0,1!" == "*" ( + echo DB_NAME=%db_name%>> .env.temp + ) else ( + set line=!line:DB_USERNAME=*! + if "!line:~0,1!" == "*" ( + echo DB_USERNAME=%db_user%>> .env.temp + ) else ( + set line=!line:DB_PASSWORD=*! + if "!line:~0,1!" == "*" ( + echo DB_PASSWORD=%db_password%>> .env.temp + ) else ( + echo %%a>> .env.temp + ) + ) + ) + ) + ) + ) + + :: Заменяем оригинальный файл + move /y .env.temp .env + + echo [32mФайл .env обновлен с параметрами внешней базы данных[0m + + :: Запускаем только контейнер с ботом + echo [36mЗапускаем Telegram Bot без контейнера базы данных...[0m + docker-compose up -d bot adminer + + echo [32mБот запущен и использует внешнюю базу данных: %db_host%:%db_port%/%db_name%[0m + echo [33mAdminer доступен по адресу: http://localhost:8080/[0m + echo [33mДанные для входа в Adminer:[0m + echo [33mСистема: PostgreSQL[0m + echo [33mСервер: %db_host%[0m + echo [33mПользователь: %db_user%[0m + echo [33mПароль: (введенный вами)[0m + echo [33mБаза данных: %db_name%[0m +) ELSE ( + :: Восстанавливаем оригинальный docker-compose.yml если есть бэкап + if exist docker-compose.yml.bak ( + move /y docker-compose.yml.bak docker-compose.yml + echo [32mdocker-compose.yml восстановлен из резервной копии[0m + ) + + echo [36mЗапускаем Telegram Bot с контейнером базы данных...[0m + + :: Проверка, запущены ли контейнеры + docker ps -q -f name=telegram-tinder-bot > tmp_containers.txt + set /p containers= .env.temp + + :: Читаем .env построчно и заменяем строку с паролем + for /f "tokens=*" %%a in (.env) do ( + set line=%%a + set line=!line:DB_PASSWORD=*! + if "!line:~0,1!" == "*" ( + echo DB_PASSWORD=%random_password%>> .env.temp + ) else ( + echo %%a>> .env.temp + ) + ) + + :: Заменяем оригинальный файл + move /y .env.temp .env + + echo [33mСгенерирован случайный пароль для базы данных и сохранен в .env[0m + ) + + echo [32mTelegram Bot запущен с локальной базой данных[0m + echo [33mAdminer доступен по адресу: http://localhost:8080/[0m + echo [33mДанные для входа в Adminer:[0m + echo [33mСистема: PostgreSQL[0m + echo [33mСервер: db[0m + echo [33mПользователь: postgres[0m + echo [33mПароль: (из переменной DB_PASSWORD в .env)[0m + echo [33mБаза данных: telegram_tinder_bot[0m +) + +:: Проверка статуса контейнеров +echo [36mПроверка статуса контейнеров:[0m +docker-compose ps + +echo ================================================ +echo [32mПроцесс запуска Telegram Tinder Bot завершен![0m +echo ================================================ +echo [33mДля просмотра логов используйте: docker-compose logs -f bot[0m +echo [33mДля остановки: docker-compose down[0m + +pause diff --git a/start.ps1 b/start.ps1 new file mode 100644 index 0000000..dd2674e --- /dev/null +++ b/start.ps1 @@ -0,0 +1,229 @@ +function createModifiedDockerCompose { + param ( + [string]$dbHost, + [string]$dbPort, + [string]$dbName, + [string]$dbUser, + [string]$dbPassword + ) + + $dockerComposeContent = @" +version: '3.8' + +services: + bot: + build: . + container_name: telegram-tinder-bot + restart: unless-stopped + env_file: .env + environment: + - NODE_ENV=production + - DB_HOST=$dbHost + - DB_PORT=$dbPort + - DB_NAME=$dbName + - DB_USERNAME=$dbUser + - DB_PASSWORD=$dbPassword + volumes: + - ./uploads:/app/uploads + - ./logs:/app/logs + networks: + - bot-network + healthcheck: + test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://localhost:3000/health"] + interval: 30s + timeout: 5s + retries: 3 + start_period: 10s + + adminer: + image: adminer:latest + container_name: adminer-tinder + restart: unless-stopped + ports: + - "8080:8080" + networks: + - bot-network + +volumes: + postgres_data: + +networks: + bot-network: + driver: bridge +"@ + + return $dockerComposeContent +} + +function restoreDockerCompose { + if (Test-Path -Path "docker-compose.yml.bak") { + Copy-Item -Path "docker-compose.yml.bak" -Destination "docker-compose.yml" -Force + Write-Host "docker-compose.yml восстановлен из резервной копии" -ForegroundColor Green + } +} + +function updateEnvFile { + param ( + [string]$dbHost, + [string]$dbPort, + [string]$dbName, + [string]$dbUser, + [string]$dbPassword + ) + + $envContent = Get-Content -Path ".env" -Raw + + $envContent = $envContent -replace "DB_HOST=.*", "DB_HOST=$dbHost" + $envContent = $envContent -replace "DB_PORT=.*", "DB_PORT=$dbPort" + $envContent = $envContent -replace "DB_NAME=.*", "DB_NAME=$dbName" + $envContent = $envContent -replace "DB_USERNAME=.*", "DB_USERNAME=$dbUser" + $envContent = $envContent -replace "DB_PASSWORD=.*", "DB_PASSWORD=$dbPassword" + + Set-Content -Path ".env" -Value $envContent + + Write-Host "Файл .env обновлен с параметрами внешней базы данных" -ForegroundColor Green +} + +function generateRandomPassword { + $length = 16 + $chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789" + $bytes = New-Object Byte[] $length + $rng = [System.Security.Cryptography.RNGCryptoServiceProvider]::new() + $rng.GetBytes($bytes) + + $password = "" + for ($i = 0; $i -lt $length; $i++) { + $password += $chars[$bytes[$i] % $chars.Length] + } + + return $password +} + +# Начало основного скрипта +Write-Host "==================================================" -ForegroundColor Cyan +Write-Host " Запуск Telegram Tinder Bot" -ForegroundColor Cyan +Write-Host "==================================================" -ForegroundColor Cyan + +# Проверка наличия Docker +try { + docker --version | Out-Null +} catch { + Write-Host "ОШИБКА: Docker не установлен!" -ForegroundColor Red + Write-Host "Установите Docker Desktop для Windows: https://docs.docker.com/desktop/install/windows-install/" + exit +} + +# Проверяем наличие .env файла +if (-not (Test-Path -Path ".env")) { + Write-Host "Файл .env не найден. Создаем из шаблона..." -ForegroundColor Yellow + + if (Test-Path -Path ".env.example") { + Copy-Item -Path ".env.example" -Destination ".env" + Write-Host "Файл .env создан из шаблона. Пожалуйста, отредактируйте его с вашими настройками." -ForegroundColor Green + } else { + Write-Host "ОШИБКА: Файл .env.example не найден. Создайте файл .env вручную." -ForegroundColor Red + exit + } +} + +# Спрашиваем про запуск базы данных +$useContainerDb = Read-Host "Запустить базу данных PostgreSQL в контейнере? (y/n)" + +if ($useContainerDb -ne "y") { + # Запрашиваем параметры подключения к внешней БД + Write-Host "Введите параметры подключения к внешней базе данных:" -ForegroundColor Cyan + $dbHost = Read-Host "Хост (например, localhost)" + $dbPort = Read-Host "Порт (например, 5432)" + $dbName = Read-Host "Имя базы данных" + $dbUser = Read-Host "Имя пользователя" + $dbPassword = Read-Host "Пароль" -AsSecureString + $dbPasswordText = [Runtime.InteropServices.Marshal]::PtrToStringAuto([Runtime.InteropServices.Marshal]::SecureStringToBSTR($dbPassword)) + + # Модифицируем docker-compose.yml + Write-Host "Модифицируем docker-compose.yml для работы с внешней базой данных..." -ForegroundColor Yellow + + # Сохраняем оригинальную версию файла + Copy-Item -Path "docker-compose.yml" -Destination "docker-compose.yml.bak" -Force + + # Создаем модифицированный docker-compose.yml + $dockerComposeContent = createModifiedDockerCompose -dbHost $dbHost -dbPort $dbPort -dbName $dbName -dbUser $dbUser -dbPassword $dbPasswordText + Set-Content -Path "docker-compose.yml" -Value $dockerComposeContent + + Write-Host "docker-compose.yml обновлен для работы с внешней базой данных" -ForegroundColor Green + + # Обновляем .env файл + Write-Host "Обновляем файл .env с параметрами внешней базы данных..." -ForegroundColor Yellow + updateEnvFile -dbHost $dbHost -dbPort $dbPort -dbName $dbName -dbUser $dbUser -dbPassword $dbPasswordText + + # Запускаем только контейнер с ботом + Write-Host "Запускаем Telegram Bot без контейнера базы данных..." -ForegroundColor Cyan + docker-compose up -d bot adminer + + Write-Host "Бот запущен и использует внешнюю базу данных: $dbHost`:$dbPort/$dbName" -ForegroundColor Green + Write-Host "Adminer доступен по адресу: http://localhost:8080/" -ForegroundColor Yellow + Write-Host "Данные для входа в Adminer:" -ForegroundColor Yellow + Write-Host "Система: PostgreSQL" -ForegroundColor Yellow + Write-Host "Сервер: $dbHost" -ForegroundColor Yellow + Write-Host "Пользователь: $dbUser" -ForegroundColor Yellow + Write-Host "Пароль: (введенный вами)" -ForegroundColor Yellow + Write-Host "База данных: $dbName" -ForegroundColor Yellow +} else { + # Восстанавливаем оригинальный docker-compose.yml если есть бэкап + restoreDockerCompose + + Write-Host "Запускаем Telegram Bot с контейнером базы данных..." -ForegroundColor Cyan + + # Проверка, запущены ли контейнеры + $containers = docker ps -q -f name=telegram-tinder-bot -f name=postgres-tinder + + if ($containers) { + $restartContainers = Read-Host "Контейнеры уже запущены. Перезапустить? (y/n)" + if ($restartContainers -eq "y") { + docker-compose down + docker-compose up -d + Write-Host "Контейнеры перезапущены" -ForegroundColor Green + } else { + Write-Host "Продолжаем работу с уже запущенными контейнерами" -ForegroundColor Cyan + } + } else { + docker-compose up -d + Write-Host "Контейнеры запущены" -ForegroundColor Green + } + + # Проверка наличия пароля для БД в .env + $envContent = Get-Content -Path ".env" -Raw + $match = [Regex]::Match($envContent, "DB_PASSWORD=(.*)(\r?\n|$)") + $dbPassword = if ($match.Success) { $match.Groups[1].Value.Trim() } else { "" } + + if ([string]::IsNullOrWhiteSpace($dbPassword)) { + # Генерируем случайный пароль + $randomPassword = generateRandomPassword + + # Обновляем .env файл + $envContent = $envContent -replace "DB_PASSWORD=.*", "DB_PASSWORD=$randomPassword" + Set-Content -Path ".env" -Value $envContent + + Write-Host "Сгенерирован случайный пароль для базы данных и сохранен в .env" -ForegroundColor Yellow + } + + Write-Host "Telegram Bot запущен с локальной базой данных" -ForegroundColor Green + Write-Host "Adminer доступен по адресу: http://localhost:8080/" -ForegroundColor Yellow + Write-Host "Данные для входа в Adminer:" -ForegroundColor Yellow + Write-Host "Система: PostgreSQL" -ForegroundColor Yellow + Write-Host "Сервер: db" -ForegroundColor Yellow + Write-Host "Пользователь: postgres" -ForegroundColor Yellow + Write-Host "Пароль: (из переменной DB_PASSWORD в .env)" -ForegroundColor Yellow + Write-Host "База данных: telegram_tinder_bot" -ForegroundColor Yellow +} + +# Проверка статуса контейнеров +Write-Host "Проверка статуса контейнеров:" -ForegroundColor Cyan +docker-compose ps + +Write-Host "==================================================" -ForegroundColor Cyan +Write-Host "Процесс запуска Telegram Tinder Bot завершен!" -ForegroundColor Green +Write-Host "==================================================" -ForegroundColor Cyan +Write-Host "Для просмотра логов используйте: docker-compose logs -f bot" -ForegroundColor Yellow +Write-Host "Для остановки: docker-compose down" -ForegroundColor Yellow + +Read-Host "Нажмите Enter для выхода" diff --git a/start.sh b/start.sh new file mode 100755 index 0000000..b458b66 --- /dev/null +++ b/start.sh @@ -0,0 +1,217 @@ +#!/bin/bash +# start.sh - Скрипт для запуска Telegram Tinder Bot +# Позволяет выбрать между локальной БД в контейнере или внешней БД + +# Цвета для вывода +GREEN='\033[0;32m' +BLUE='\033[0;34m' +YELLOW='\033[0;33m' +RED='\033[0;31m' +NC='\033[0m' # No Color + +echo -e "${BLUE}==================================================${NC}" +echo -e "${BLUE} Запуск Telegram Tinder Bot ${NC}" +echo -e "${BLUE}==================================================${NC}" + +# Проверка наличия Docker и Docker Compose +if ! command -v docker &> /dev/null || ! command -v docker-compose &> /dev/null; then + echo -e "${RED}ОШИБКА: Docker и/или Docker Compose не установлены!${NC}" + echo -e "Для установки Docker следуйте инструкции на: https://docs.docker.com/get-docker/" + exit 1 +fi + +# Проверяем наличие .env файла +if [ ! -f .env ]; then + echo -e "${YELLOW}Файл .env не найден. Создаем из шаблона...${NC}" + if [ -f .env.example ]; then + cp .env.example .env + echo -e "${GREEN}Файл .env создан из шаблона. Пожалуйста, отредактируйте его с вашими настройками.${NC}" + else + echo -e "${RED}ОШИБКА: Файл .env.example не найден. Создайте файл .env вручную.${NC}" + exit 1 + fi +fi + +# Спрашиваем про запуск базы данных +read -p "Запустить базу данных PostgreSQL в контейнере? (y/n): " use_container_db + +# Функция для изменения docker-compose.yml +modify_docker_compose() { + local host=$1 + local port=$2 + local user=$3 + local password=$4 + local db_name=$5 + + echo -e "${YELLOW}Модифицируем docker-compose.yml для работы с внешней базой данных...${NC}" + + # Сохраняем оригинальную версию файла + cp docker-compose.yml docker-compose.yml.bak + + # Создаем временный файл с модифицированным содержимым + cat > docker-compose.temp.yml << EOL +version: '3.8' + +services: + bot: + build: . + container_name: telegram-tinder-bot + restart: unless-stopped + env_file: .env + environment: + - NODE_ENV=production + - DB_HOST=${host} + - DB_PORT=${port} + - DB_NAME=${db_name} + - DB_USERNAME=${user} + - DB_PASSWORD=${password} + volumes: + - ./uploads:/app/uploads + - ./logs:/app/logs + networks: + - bot-network + healthcheck: + test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://localhost:3000/health"] + interval: 30s + timeout: 5s + retries: 3 + start_period: 10s + + adminer: + image: adminer:latest + container_name: adminer-tinder + restart: unless-stopped + ports: + - "8080:8080" + networks: + - bot-network + +volumes: + postgres_data: + +networks: + bot-network: + driver: bridge +EOL + + # Заменяем оригинальный файл + mv docker-compose.temp.yml docker-compose.yml + + echo -e "${GREEN}docker-compose.yml обновлен для работы с внешней базой данных${NC}" +} + +# Функция для восстановления docker-compose.yml +restore_docker_compose() { + if [ -f docker-compose.yml.bak ]; then + mv docker-compose.yml.bak docker-compose.yml + echo -e "${GREEN}docker-compose.yml восстановлен из резервной копии${NC}" + fi +} + +# Обработка выбора +if [[ "$use_container_db" =~ ^[Nn]$ ]]; then + # Запрашиваем параметры подключения к внешней БД + echo -e "${BLUE}Введите параметры подключения к внешней базе данных:${NC}" + read -p "Хост (например, localhost): " db_host + read -p "Порт (например, 5432): " db_port + read -p "Имя базы данных: " db_name + read -p "Имя пользователя: " db_user + read -p "Пароль: " db_password + + # Модифицируем docker-compose.yml + modify_docker_compose "$db_host" "$db_port" "$db_user" "$db_password" "$db_name" + + # Обновляем .env файл + echo -e "${YELLOW}Обновляем файл .env с параметрами внешней базы данных...${NC}" + + # Используем sed для замены переменных в .env + if [[ "$OSTYPE" == "darwin"* ]]; then + # macOS требует другой синтаксис для sed + sed -i '' "s/DB_HOST=.*/DB_HOST=${db_host}/" .env + sed -i '' "s/DB_PORT=.*/DB_PORT=${db_port}/" .env + sed -i '' "s/DB_NAME=.*/DB_NAME=${db_name}/" .env + sed -i '' "s/DB_USERNAME=.*/DB_USERNAME=${db_user}/" .env + sed -i '' "s/DB_PASSWORD=.*/DB_PASSWORD=${db_password}/" .env + else + # Linux и другие системы + sed -i "s/DB_HOST=.*/DB_HOST=${db_host}/" .env + sed -i "s/DB_PORT=.*/DB_PORT=${db_port}/" .env + sed -i "s/DB_NAME=.*/DB_NAME=${db_name}/" .env + sed -i "s/DB_USERNAME=.*/DB_USERNAME=${db_user}/" .env + sed -i "s/DB_PASSWORD=.*/DB_PASSWORD=${db_password}/" .env + fi + + echo -e "${GREEN}Файл .env обновлен с параметрами внешней базы данных${NC}" + + # Запускаем только контейнер с ботом + echo -e "${BLUE}Запускаем Telegram Bot без контейнера базы данных...${NC}" + docker-compose up -d bot adminer + + echo -e "${GREEN}Бот запущен и использует внешнюю базу данных: ${db_host}:${db_port}/${db_name}${NC}" + echo -e "${YELLOW}Adminer доступен по адресу: http://localhost:8080/${NC}" + echo -e "${YELLOW}Данные для входа в Adminer:${NC}" + echo -e "${YELLOW}Система: PostgreSQL${NC}" + echo -e "${YELLOW}Сервер: ${db_host}${NC}" + echo -e "${YELLOW}Пользователь: ${db_user}${NC}" + echo -e "${YELLOW}Пароль: (введенный вами)${NC}" + echo -e "${YELLOW}База данных: ${db_name}${NC}" +else + # Восстанавливаем оригинальный docker-compose.yml если есть бэкап + restore_docker_compose + + echo -e "${BLUE}Запускаем Telegram Bot с контейнером базы данных...${NC}" + + # Проверка, запущены ли контейнеры + containers=$(docker ps -q -f name=telegram-tinder-bot -f name=postgres-tinder) + if [ -n "$containers" ]; then + echo -e "${YELLOW}Контейнеры уже запущены. Перезапустить? (y/n): ${NC}" + read restart_containers + if [[ "$restart_containers" =~ ^[Yy]$ ]]; then + docker-compose down + docker-compose up -d + echo -e "${GREEN}Контейнеры перезапущены${NC}" + else + echo -e "${BLUE}Продолжаем работу с уже запущенными контейнерами${NC}" + fi + else + docker-compose up -d + echo -e "${GREEN}Контейнеры запущены${NC}" + fi + + # Проверка наличия пароля для БД в .env + db_password=$(grep DB_PASSWORD .env | cut -d '=' -f2) + if [ -z "$db_password" ]; then + # Генерируем случайный пароль + random_password=$(head /dev/urandom | tr -dc A-Za-z0-9 | head -c 16) + + # Обновляем .env файл + if [[ "$OSTYPE" == "darwin"* ]]; then + # macOS требует другой синтаксис для sed + sed -i '' "s/DB_PASSWORD=.*/DB_PASSWORD=${random_password}/" .env + else + # Linux и другие системы + sed -i "s/DB_PASSWORD=.*/DB_PASSWORD=${random_password}/" .env + fi + + echo -e "${YELLOW}Сгенерирован случайный пароль для базы данных и сохранен в .env${NC}" + fi + + echo -e "${GREEN}Telegram Bot запущен с локальной базой данных${NC}" + echo -e "${YELLOW}Adminer доступен по адресу: http://localhost:8080/${NC}" + echo -e "${YELLOW}Данные для входа в Adminer:${NC}" + echo -e "${YELLOW}Система: PostgreSQL${NC}" + echo -e "${YELLOW}Сервер: db${NC}" + echo -e "${YELLOW}Пользователь: postgres${NC}" + echo -e "${YELLOW}Пароль: (из переменной DB_PASSWORD в .env)${NC}" + echo -e "${YELLOW}База данных: telegram_tinder_bot${NC}" +fi + +# Проверка статуса контейнеров +echo -e "${BLUE}Проверка статуса контейнеров:${NC}" +docker-compose ps + +echo -e "${BLUE}==================================================${NC}" +echo -e "${GREEN}Процесс запуска Telegram Tinder Bot завершен!${NC}" +echo -e "${BLUE}==================================================${NC}" +echo -e "${YELLOW}Для просмотра логов используйте: docker-compose logs -f bot${NC}" +echo -e "${YELLOW}Для остановки: docker-compose down${NC}" diff --git a/test-connection.js b/test-connection.js deleted file mode 100644 index 56810cd..0000000 --- a/test-connection.js +++ /dev/null @@ -1,37 +0,0 @@ -require('dotenv').config(); -const { Pool } = require('pg'); - -// Используем параметры напрямую из .env -const pool = new Pool({ - host: process.env.DB_HOST, - port: process.env.DB_PORT, - database: process.env.DB_NAME, - user: process.env.DB_USERNAME, - password: process.env.DB_PASSWORD, - max: 5, - connectionTimeoutMillis: 5000 -}); - -console.log('DB Connection Details:'); -console.log(`- Host: ${process.env.DB_HOST}`); -console.log(`- Port: ${process.env.DB_PORT}`); -console.log(`- Database: ${process.env.DB_NAME}`); -console.log(`- User: ${process.env.DB_USERNAME}`); - -async function testConnection() { - try { - const client = await pool.connect(); - try { - const result = await client.query('SELECT NOW() as current_time'); - console.log('✅ Connected to database successfully!'); - console.log(`Current database time: ${result.rows[0].current_time}`); - } finally { - client.release(); - } - await pool.end(); - } catch (error) { - console.error('❌ Failed to connect to database:', error); - } -} - -testConnection(); diff --git a/test-db.js b/test-db.js deleted file mode 100644 index e69de29..0000000