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
|
TELEGRAM_BOT_TOKEN=your_telegram_bot_token_here
|
||||||
|
|
||||||
# Database Configuration
|
# Database Configuration
|
||||||
|
# For local development (when running the bot directly)
|
||||||
DB_HOST=localhost
|
DB_HOST=localhost
|
||||||
DB_PORT=5432
|
DB_PORT=5432
|
||||||
DB_NAME=telegram_tinder_bot
|
DB_NAME=telegram_tinder_bot
|
||||||
DB_USERNAME=postgres
|
DB_USERNAME=postgres
|
||||||
DB_PASSWORD=your_password_here
|
DB_PASSWORD=your_password_here
|
||||||
|
|
||||||
# Application Settings
|
# === APPLICATION SETTINGS ===
|
||||||
|
|
||||||
|
# Environment (development, production)
|
||||||
NODE_ENV=development
|
NODE_ENV=development
|
||||||
|
|
||||||
|
# Port for health checks
|
||||||
PORT=3000
|
PORT=3000
|
||||||
|
|
||||||
# Optional: Redis for caching (if using)
|
# === FILE UPLOAD SETTINGS ===
|
||||||
REDIS_HOST=localhost
|
|
||||||
REDIS_PORT=6379
|
|
||||||
REDIS_PASSWORD=
|
|
||||||
|
|
||||||
# Optional: File upload settings
|
# Path for storing uploaded files
|
||||||
UPLOAD_PATH=./uploads
|
UPLOAD_PATH=./uploads
|
||||||
|
|
||||||
|
# Maximum file size for uploads (in bytes, default: 5MB)
|
||||||
MAX_FILE_SIZE=5242880
|
MAX_FILE_SIZE=5242880
|
||||||
|
|
||||||
# Optional: External services
|
# === LOGGING ===
|
||||||
GOOGLE_MAPS_API_KEY=your_google_maps_key
|
|
||||||
CLOUDINARY_URL=your_cloudinary_url
|
|
||||||
|
|
||||||
# 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
|
JWT_SECRET=your_jwt_secret_here
|
||||||
|
|
||||||
|
# Encryption key for sensitive data
|
||||||
ENCRYPTION_KEY=your_encryption_key_here
|
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 package*.json ./
|
||||||
COPY tsconfig.json ./
|
COPY tsconfig.json ./
|
||||||
|
|
||||||
# Install dependencies
|
# Install all dependencies (including devDependencies for build)
|
||||||
RUN npm ci --only=production && npm cache clean --force
|
RUN npm ci && npm cache clean --force
|
||||||
|
|
||||||
# Copy source code
|
# Copy source code
|
||||||
COPY src/ ./src/
|
COPY src/ ./src/
|
||||||
|
COPY .env.example ./
|
||||||
|
|
||||||
# Build the application
|
# Build the application
|
||||||
RUN npm run build
|
RUN npm run build
|
||||||
@@ -31,11 +32,19 @@ RUN npm ci --only=production && npm cache clean --force
|
|||||||
|
|
||||||
# Copy built application from builder stage
|
# Copy built application from builder stage
|
||||||
COPY --from=builder /app/dist ./dist
|
COPY --from=builder /app/dist ./dist
|
||||||
|
COPY --from=builder /app/.env.example ./.env.example
|
||||||
|
|
||||||
# Copy configuration files
|
# Copy database migrations
|
||||||
COPY config/ ./config/
|
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
|
RUN mkdir -p uploads logs
|
||||||
|
|
||||||
# Create non-root user for security
|
# Create non-root user for security
|
||||||
@@ -53,7 +62,7 @@ EXPOSE 3000
|
|||||||
|
|
||||||
# Health check
|
# Health check
|
||||||
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
|
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
|
# Start the application with migration script
|
||||||
CMD ["node", "dist/bot.js"]
|
CMD ["./startup.sh"]
|
||||||
|
|||||||
43
README.md
43
README.md
@@ -268,8 +268,32 @@ npm run dev
|
|||||||
- Node.js 16+
|
- Node.js 16+
|
||||||
- PostgreSQL 12+
|
- PostgreSQL 12+
|
||||||
- Telegram Bot Token (получить у [@BotFather](https://t.me/BotFather))
|
- 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
|
```bash
|
||||||
# Клонировать репозиторий
|
# Клонировать репозиторий
|
||||||
@@ -279,24 +303,17 @@ cd telegram-tinder-bot
|
|||||||
# Установить зависимости
|
# Установить зависимости
|
||||||
npm install
|
npm install
|
||||||
|
|
||||||
# Скомпилировать TypeScript
|
# Скопировать файл конфигурации
|
||||||
npm run build
|
cp .env.example .env
|
||||||
```
|
# Отредактируйте файл .env и укажите свой TELEGRAM_BOT_TOKEN
|
||||||
|
|
||||||
### 3. Настройка базы данных
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Создать базу данных PostgreSQL
|
# Создать базу данных PostgreSQL
|
||||||
createdb telegram_tinder_bot
|
createdb telegram_tinder_bot
|
||||||
|
|
||||||
# Запустить миграции
|
# Запустить миграции
|
||||||
psql -d telegram_tinder_bot -f src/database/migrations/init.sql
|
npm run migrate:up
|
||||||
```
|
|
||||||
|
|
||||||
### 4. Запуск бота
|
# Скомпилировать TypeScript
|
||||||
|
|
||||||
```bash
|
|
||||||
# Компиляция TypeScript
|
|
||||||
npm run build
|
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
|
container_name: telegram-tinder-bot
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
depends_on:
|
depends_on:
|
||||||
- db
|
db:
|
||||||
|
condition: service_healthy
|
||||||
|
env_file: .env
|
||||||
environment:
|
environment:
|
||||||
- NODE_ENV=production
|
- NODE_ENV=production
|
||||||
- DB_HOST={DB_HOST}
|
- DB_HOST=db
|
||||||
- DB_PORT={DB_PORT}
|
- DB_PORT=5432
|
||||||
- DB_NAME={DB_NAME}
|
- DB_NAME=telegram_tinder_bot
|
||||||
- DB_USERNAME={DB_USERNAME}
|
- DB_USERNAME=postgres
|
||||||
- DB_PASSWORD={DB_PASSWORD}
|
|
||||||
- TELEGRAM_BOT_TOKEN={TELEGRAM_BOT_TOKEN}
|
|
||||||
volumes:
|
volumes:
|
||||||
- ./uploads:/app/uploads
|
- ./uploads:/app/uploads
|
||||||
|
- ./logs:/app/logs
|
||||||
networks:
|
networks:
|
||||||
- bot-network
|
- 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:
|
db:
|
||||||
image: postgres:15-alpine
|
image: postgres:15-alpine
|
||||||
@@ -27,14 +34,18 @@ services:
|
|||||||
environment:
|
environment:
|
||||||
- POSTGRES_DB=telegram_tinder_bot
|
- POSTGRES_DB=telegram_tinder_bot
|
||||||
- POSTGRES_USER=postgres
|
- POSTGRES_USER=postgres
|
||||||
- POSTGRES_PASSWORD=password123
|
- POSTGRES_PASSWORD=${DB_PASSWORD:-password123}
|
||||||
volumes:
|
volumes:
|
||||||
- postgres_data:/var/lib/postgresql/data
|
- postgres_data:/var/lib/postgresql/data
|
||||||
- ./src/database/migrations/init.sql:/docker-entrypoint-initdb.d/init.sql
|
|
||||||
ports:
|
ports:
|
||||||
- "5433:5432"
|
- "5433:5432"
|
||||||
networks:
|
networks:
|
||||||
- bot-network
|
- bot-network
|
||||||
|
healthcheck:
|
||||||
|
test: ["CMD-SHELL", "pg_isready -U postgres"]
|
||||||
|
interval: 5s
|
||||||
|
timeout: 5s
|
||||||
|
retries: 5
|
||||||
|
|
||||||
adminer:
|
adminer:
|
||||||
image: adminer:latest
|
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`
|
Скрипты JavaScript можно запускать с помощью Node.js:
|
||||||
Проверяет и создает необходимые таблицы и столбцы в базе данных:
|
|
||||||
- Таблицы `notifications`, `scheduled_notifications`, `notification_templates`
|
|
||||||
- Столбцы `notification_settings`, `state`, `state_data` в таблице `users`
|
|
||||||
|
|
||||||
### 2. `update_bot_with_notifications.js`
|
|
||||||
Обновляет файл `bot.ts`:
|
|
||||||
- Добавляет импорт класса `NotificationHandlers`
|
|
||||||
- Добавляет объявление поля `notificationHandlers` в класс `TelegramTinderBot`
|
|
||||||
- Добавляет создание экземпляра `NotificationHandlers` в конструкторе
|
|
||||||
- Добавляет регистрацию обработчиков уведомлений в методе `registerHandlers`
|
|
||||||
|
|
||||||
### 3. `fix_all_notifications.js`
|
|
||||||
Запускает оба скрипта последовательно для полного исправления проблемы
|
|
||||||
|
|
||||||
## Как использовать
|
|
||||||
|
|
||||||
1. Остановите бота, если он запущен:
|
|
||||||
```bash
|
```bash
|
||||||
# Нажмите Ctrl+C в терминале, где запущен бот
|
node scripts/script-name.js
|
||||||
# или найдите процесс и завершите его
|
|
||||||
```
|
```
|
||||||
|
|
||||||
2. Запустите комплексный скрипт исправления:
|
Bash скрипты должны быть сделаны исполняемыми:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
node scripts/fix_all_notifications.js
|
chmod +x scripts/script-name.sh
|
||||||
|
./scripts/script-name.sh
|
||||||
```
|
```
|
||||||
|
|
||||||
3. После успешного выполнения скрипта перезапустите бота:
|
## Добавление новых скриптов
|
||||||
|
|
||||||
|
При добавлении новых скриптов соблюдайте следующие правила:
|
||||||
|
1. Используйте понятное имя файла, отражающее его назначение
|
||||||
|
2. Добавьте комментарии в начало файла с описанием его функциональности
|
||||||
|
3. Добавьте запись об этом скрипте в текущий файл README.md
|
||||||
|
|
||||||
|
## Скрипты миграций
|
||||||
|
|
||||||
|
Миграции базы данных следует создавать с помощью команды:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
npm run start
|
npm run migrate:create your_migration_name
|
||||||
```
|
```
|
||||||
|
|
||||||
## Проверка результата
|
Это создаст файл миграции в директории `/migrations`.
|
||||||
|
|
||||||
После запуска бота убедитесь, что:
|
|
||||||
1. Бот отвечает на все callback запросы (включая кнопки, не связанные с уведомлениями)
|
|
||||||
2. Настройки уведомлений работают корректно (команда /notifications или кнопка в меню настроек)
|
|
||||||
3. Уведомления о лайках, супер-лайках и новых матчах приходят пользователям
|
|
||||||
|
|
||||||
## Если проблемы остались
|
|
||||||
|
|
||||||
Если после выполнения всех шагов проблемы остались, выполните следующие проверки:
|
|
||||||
|
|
||||||
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-запросы
|
|
||||||
|
|
||||||
В случае обнаружения ошибок, исправьте их вручную и перезапустите бота.
|
|
||||||
|
|||||||
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