pre-deploy commit

This commit is contained in:
2025-09-18 14:19:49 +09:00
parent 5ea3e8c1f3
commit 713eadc643
50 changed files with 2238 additions and 569 deletions

View 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);
});

View 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
View 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`.

View File

@@ -0,0 +1,44 @@
// add-premium-columns.js
// Скрипт для добавления колонок premium и premium_expires_at в таблицу users
const { Pool } = require('pg');
// Настройки подключения к базе данных - используем те же настройки, что и раньше
const pool = new Pool({
host: '192.168.0.102',
port: 5432,
database: 'telegram_tinder_bot',
user: 'trevor',
password: 'Cl0ud_1985!'
});
async function addPremiumColumns() {
try {
console.log('Подключение к базе данных...');
const client = await pool.connect();
console.log('Добавление колонок premium и premium_expires_at в таблицу users...');
// SQL запрос для добавления колонок
const sql = `
ALTER TABLE users
ADD COLUMN IF NOT EXISTS premium BOOLEAN DEFAULT FALSE,
ADD COLUMN IF NOT EXISTS premium_expires_at TIMESTAMP;
`;
await client.query(sql);
console.log('✅ Колонки premium и premium_expires_at успешно добавлены в таблицу users');
// Закрытие соединения
client.release();
await pool.end();
console.log('Подключение к базе данных закрыто');
} catch (error) {
console.error('❌ Ошибка при добавлении колонок:', error);
await pool.end();
process.exit(1);
}
}
// Запуск функции
addPremiumColumns();

View File

@@ -0,0 +1,40 @@
// add-premium-columns.js
// Скрипт для добавления колонок premium и premium_expires_at в таблицу users
const { Client } = require('pg');
// Настройки подключения к базе данных
const client = new Client({
host: '192.168.0.102',
port: 5432,
user: 'trevor',
password: 'Cl0ud_1985!',
database: 'telegram_tinder_bot'
});
async function addPremiumColumns() {
try {
await client.connect();
console.log('Подключение к базе данных успешно установлено');
// SQL запрос для добавления колонок
const sql = `
ALTER TABLE users
ADD COLUMN IF NOT EXISTS premium BOOLEAN DEFAULT FALSE,
ADD COLUMN IF NOT EXISTS premium_expires_at TIMESTAMP;
`;
await client.query(sql);
console.log('Колонки premium и premium_expires_at успешно добавлены в таблицу users');
// Закрываем подключение
await client.end();
console.log('Подключение к базе данных закрыто');
} catch (error) {
console.error('Ошибка при добавлении колонок:', error);
await client.end();
}
}
// Запуск функции
addPremiumColumns();

View File

@@ -0,0 +1,28 @@
// add-premium-columns.ts
// Скрипт для добавления колонок premium и premium_expires_at в таблицу users
import { query } from '../src/database/connection';
async function addPremiumColumns() {
try {
console.log('Добавление колонок premium и premium_expires_at в таблицу users...');
// SQL запрос для добавления колонок
const sql = `
ALTER TABLE users
ADD COLUMN IF NOT EXISTS premium BOOLEAN DEFAULT FALSE,
ADD COLUMN IF NOT EXISTS premium_expires_at TIMESTAMP;
`;
await query(sql);
console.log('✅ Колонки premium и premium_expires_at успешно добавлены в таблицу users');
process.exit(0);
} catch (error) {
console.error('❌ Ошибка при добавлении колонок:', error);
process.exit(1);
}
}
// Запуск функции
addPremiumColumns();

View File

@@ -0,0 +1,58 @@
// Скрипт для добавления колонки premium в таблицу users и установки premium для всех пользователей
require('dotenv').config();
const { Pool } = require('pg');
// Создаем пул соединений
const pool = new Pool({
user: process.env.DB_USERNAME,
host: process.env.DB_HOST,
database: process.env.DB_NAME,
password: process.env.DB_PASSWORD,
port: parseInt(process.env.DB_PORT || '5432')
});
async function setAllUsersToPremium() {
try {
console.log('Проверяем наличие столбца premium в таблице users...');
const result = await pool.query(`
SELECT EXISTS (
SELECT FROM information_schema.columns
WHERE table_schema = 'public'
AND table_name = 'users'
AND column_name = 'premium'
);
`);
if (!result.rows[0].exists) {
console.log('🔄 Добавляем столбец premium...');
await pool.query(`ALTER TABLE users ADD COLUMN premium BOOLEAN DEFAULT false;`);
console.log('✅ Столбец premium успешно добавлен');
} else {
console.log('✅ Столбец premium уже существует');
}
console.log('Устанавливаем премиум-статус для всех пользователей...');
const updateResult = await pool.query(`
UPDATE users
SET premium = true
WHERE true
RETURNING id, telegram_id, premium
`);
console.log(`✅ Успешно установлен премиум-статус для ${updateResult.rows.length} пользователей:`);
updateResult.rows.forEach(row => {
console.log(`ID: ${row.id.substr(0, 8)}... | Telegram ID: ${row.telegram_id} | Premium: ${row.premium}`);
});
console.log('🎉 Все пользователи теперь имеют премиум-статус!');
} catch (error) {
console.error('Ошибка при установке премиум-статуса:', error);
} finally {
await pool.end();
console.log('Соединение с базой данных закрыто');
}
}
setAllUsersToPremium();

View File

@@ -0,0 +1,73 @@
// Скрипт для установки премиум-статуса всем пользователям
require('dotenv').config();
const { Pool } = require('pg');
// Проверяем и выводим параметры подключения
console.log('Параметры подключения к БД:');
console.log('DB_USERNAME:', process.env.DB_USERNAME);
console.log('DB_HOST:', process.env.DB_HOST);
console.log('DB_NAME:', process.env.DB_NAME);
console.log('DB_PASSWORD:', process.env.DB_PASSWORD ? '[указан]' : '[не указан]');
console.log('DB_PORT:', process.env.DB_PORT);
// Создаем пул соединений
const pool = new Pool({
user: process.env.DB_USERNAME,
host: process.env.DB_HOST,
database: process.env.DB_NAME,
password: process.env.DB_PASSWORD,
port: parseInt(process.env.DB_PORT || '5432')
});
async function setAllUsersToPremium() {
try {
console.log('Устанавливаем премиум-статус для всех пользователей...');
// Проверка соединения с БД
console.log('Проверка соединения с БД...');
const testResult = await pool.query('SELECT NOW()');
console.log('✅ Соединение успешно:', testResult.rows[0].now);
// Проверка наличия столбца premium
console.log('Проверяем наличие столбца premium в таблице users...');
const checkResult = await pool.query(`
SELECT EXISTS (
SELECT FROM information_schema.columns
WHERE table_schema = 'public'
AND table_name = 'users'
AND column_name = 'premium'
);
`);
if (!checkResult.rows[0].exists) {
console.log('🔄 Добавляем столбец premium...');
await pool.query(`ALTER TABLE users ADD COLUMN premium BOOLEAN DEFAULT false;`);
console.log('✅ Столбец premium успешно добавлен');
} else {
console.log('✅ Столбец premium уже существует');
}
// Устанавливаем premium=true для всех пользователей
const updateResult = await pool.query(`
UPDATE users
SET premium = true
WHERE true
RETURNING id, telegram_id, premium
`);
console.log(`✅ Успешно установлен премиум-статус для ${updateResult.rows.length} пользователей:`);
updateResult.rows.forEach(row => {
console.log(`ID: ${row.id.substr(0, 8)}... | Telegram ID: ${row.telegram_id} | Premium: ${row.premium}`);
});
console.log('🎉 Все пользователи теперь имеют премиум-статус!');
} catch (error) {
console.error('❌ Ошибка при установке премиум-статуса:', error);
} finally {
await pool.end();
console.log('Соединение с базой данных закрыто');
}
}
setAllUsersToPremium();