pre-deploy commit
This commit is contained in:
50
.env.example
50
.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
|
||||
|
||||
68
.env.production
Normal file
68
.env.production
Normal file
@@ -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
|
||||
25
Dockerfile
25
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"]
|
||||
|
||||
43
README.md
43
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 <repository-url>
|
||||
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
|
||||
|
||||
# Запуск бота
|
||||
|
||||
54
bin/backup_db.sh
Normal file
54
bin/backup_db.sh
Normal file
@@ -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
|
||||
72
bin/create_release.sh
Normal file
72
bin/create_release.sh
Normal file
@@ -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"
|
||||
58
bin/install_docker.sh
Normal file
58
bin/install_docker.sh
Normal file
@@ -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!"
|
||||
@@ -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();
|
||||
55
deploy.sh
Normal file
55
deploy.sh
Normal file
@@ -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"
|
||||
69
docker-compose.override.yml.example
Normal file
69
docker-compose.override.yml.example
Normal file
@@ -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
|
||||
@@ -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
|
||||
|
||||
264
docs/PRODUCTION_DEPLOYMENT.md
Normal file
264
docs/PRODUCTION_DEPLOYMENT.md
Normal file
@@ -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 для веб-интерфейса
|
||||
- [ ] (Опционально) Настроено автоматическое обновление
|
||||
90
fixes.md
90
fixes.md
@@ -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]);
|
||||
```
|
||||
|
||||
## Примечание
|
||||
|
||||
После внесения исправлений рекомендуется проверить все остальные места в коде, где могут использоваться эти имена столбцов, и убедиться в их согласованности.
|
||||
@@ -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();
|
||||
@@ -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<boolean> {
|
||||
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<string[]> {
|
||||
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<boolean> {
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -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`.
|
||||
|
||||
16
scripts/startup.sh
Normal file
16
scripts/startup.sh
Normal file
@@ -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
|
||||
BIN
set-premium.js
BIN
set-premium.js
Binary file not shown.
404
sql/consolidated.sql
Normal file
404
sql/consolidated.sql
Normal file
@@ -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;
|
||||
108
src/database/migrateOnStartup.ts
Normal file
108
src/database/migrateOnStartup.ts
Normal file
@@ -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);
|
||||
});
|
||||
182
src/database/migrations/consolidated.sql
Normal file
182
src/database/migrations/consolidated.sql
Normal file
@@ -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);
|
||||
62
src/premium/README.md
Normal file
62
src/premium/README.md
Normal file
@@ -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`.
|
||||
241
start.bat
Normal file
241
start.bat
Normal file
@@ -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=<tmp_containers.txt
|
||||
del tmp_containers.txt
|
||||
|
||||
if not "%containers%" == "" (
|
||||
set /p restart_containers="Контейнеры уже запущены. Перезапустить? (y/n): "
|
||||
if /I "%restart_containers%" == "y" (
|
||||
docker-compose down
|
||||
docker-compose up -d
|
||||
echo [32mКонтейнеры перезапущены[0m
|
||||
) else (
|
||||
echo [36mПродолжаем работу с уже запущенными контейнерами[0m
|
||||
)
|
||||
) else (
|
||||
docker-compose up -d
|
||||
echo [32mКонтейнеры запущены[0m
|
||||
)
|
||||
|
||||
:: Проверка наличия пароля для БД в .env
|
||||
findstr /C:"DB_PASSWORD=" .env > tmp_db_password.txt
|
||||
set /p db_password_line=<tmp_db_password.txt
|
||||
del tmp_db_password.txt
|
||||
|
||||
:: Проверяем, есть ли значение после DB_PASSWORD=
|
||||
for /f "tokens=2 delims==" %%i in ("%db_password_line%") do (
|
||||
set db_password=%%i
|
||||
)
|
||||
|
||||
if "%db_password%" == "" (
|
||||
:: Генерируем случайный пароль
|
||||
set chars=ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789
|
||||
set random_password=
|
||||
for /L %%i in (1,1,16) do (
|
||||
set /a random_index=!random! %% 62
|
||||
for %%j in (!random_index!) do set random_password=!random_password!!chars:~%%j,1!
|
||||
)
|
||||
|
||||
:: Обновляем .env файл с новым паролем
|
||||
:: Создаем временный файл
|
||||
type NUL > .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
|
||||
229
start.ps1
Normal file
229
start.ps1
Normal file
@@ -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 для выхода"
|
||||
217
start.sh
Executable file
217
start.sh
Executable file
@@ -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}"
|
||||
@@ -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();
|
||||
Reference in New Issue
Block a user