4.0.0: Исправлена обработка старых дампов
All checks were successful
Build and Push Docker Image / build-and-push (push) Successful in 1m19s
All checks were successful
Build and Push Docker Image / build-and-push (push) Successful in 1m19s
This commit is contained in:
120
play-life-backend/MIGRATION_BASELINE.md
Normal file
120
play-life-backend/MIGRATION_BASELINE.md
Normal file
@@ -0,0 +1,120 @@
|
||||
# Инструкция по применению baseline миграции
|
||||
|
||||
## Обзор
|
||||
|
||||
После перехода на `golang-migrate` текущая схема БД была зафиксирована как baseline миграция `000001_baseline.up.sql`. Для существующих баз данных baseline миграция **не должна применяться автоматически** - вместо этого нужно использовать команду `migrate force` для установки текущей версии миграции.
|
||||
|
||||
## Для существующих баз данных
|
||||
|
||||
### Шаг 1: Создание backup
|
||||
|
||||
**ОБЯЗАТЕЛЬНО** создайте backup базы данных перед применением baseline:
|
||||
|
||||
```bash
|
||||
# Используйте существующий скрипт dump-db.sh
|
||||
./dump-db.sh
|
||||
|
||||
# Или вручную:
|
||||
pg_dump -h $DB_HOST -U $DB_USER -d $DB_NAME > backup_$(date +%Y%m%d_%H%M%S).sql
|
||||
```
|
||||
|
||||
### Шаг 2: Установка версии миграции
|
||||
|
||||
Для существующих баз данных нужно установить версию миграции в `1` (baseline), не применяя саму миграцию:
|
||||
|
||||
```bash
|
||||
# Установите переменные окружения
|
||||
export DB_HOST=localhost
|
||||
export DB_PORT=5432
|
||||
export DB_USER=playeng
|
||||
export DB_PASSWORD=playeng
|
||||
export DB_NAME=playeng
|
||||
|
||||
# Установите версию миграции в 1 (baseline)
|
||||
migrate -path ./play-life-backend/migrations \
|
||||
-database "postgres://$DB_USER:$DB_PASSWORD@$DB_HOST:$DB_PORT/$DB_NAME?sslmode=disable" \
|
||||
force 1
|
||||
```
|
||||
|
||||
**Важно:** Команда `force 1` устанавливает версию миграции в `1`, но **не выполняет** SQL из baseline миграции. Это правильно, так как схема уже существует.
|
||||
|
||||
### Шаг 3: Проверка
|
||||
|
||||
Проверьте, что версия миграции установлена правильно:
|
||||
|
||||
```bash
|
||||
migrate -path ./play-life-backend/migrations \
|
||||
-database "postgres://$DB_USER:$DB_PASSWORD@$DB_HOST:$DB_PORT/$DB_NAME?sslmode=disable" \
|
||||
version
|
||||
```
|
||||
|
||||
Должно вывести: `1 (dirty)`
|
||||
|
||||
Если выводит `1 (dirty)`, это нормально - это означает, что версия установлена, но миграция не была применена (что и требуется для baseline).
|
||||
|
||||
### Шаг 4: Очистка dirty флага (опционально)
|
||||
|
||||
Если нужно убрать dirty флаг:
|
||||
|
||||
```bash
|
||||
migrate -path ./play-life-backend/migrations \
|
||||
-database "postgres://$DB_USER:$DB_PASSWORD@$DB_HOST:$DB_PORT/$DB_NAME?sslmode=disable" \
|
||||
force 1
|
||||
```
|
||||
|
||||
## Для новых баз данных
|
||||
|
||||
Для новых баз данных baseline миграция применится автоматически при первом запуске приложения через функцию `runMigrations()`.
|
||||
|
||||
Или вручную:
|
||||
|
||||
```bash
|
||||
migrate -path ./play-life-backend/migrations \
|
||||
-database "postgres://$DB_USER:$DB_PASSWORD@$DB_HOST:$DB_PORT/$DB_NAME?sslmode=disable" \
|
||||
up
|
||||
```
|
||||
|
||||
## Проверка схемы
|
||||
|
||||
После применения baseline (или установки версии для существующих БД) можно проверить схему:
|
||||
|
||||
```bash
|
||||
# Экспорт схемы
|
||||
pg_dump -h $DB_HOST -U $DB_USER -d $DB_NAME --schema-only > current_schema.sql
|
||||
|
||||
# Сравнение с baseline (если нужно)
|
||||
diff current_schema.sql play-life-backend/migrations/000001_baseline.up.sql
|
||||
```
|
||||
|
||||
## Важные замечания
|
||||
|
||||
1. **Никогда не применяйте baseline миграцию на существующих БД** - используйте только `migrate force 1`
|
||||
2. **Всегда создавайте backup** перед любыми операциями с миграциями
|
||||
3. **Проверяйте версию миграции** после установки baseline
|
||||
4. **Новые миграции** будут применяться автоматически при запуске приложения
|
||||
|
||||
## Устранение проблем
|
||||
|
||||
### Ошибка "dirty database version"
|
||||
|
||||
Если база данных находится в состоянии "dirty", исправьте это:
|
||||
|
||||
```bash
|
||||
migrate -path ./play-life-backend/migrations \
|
||||
-database "postgres://$DB_USER:$DB_PASSWORD@$DB_HOST:$DB_PORT/$DB_NAME?sslmode=disable" \
|
||||
force <version>
|
||||
```
|
||||
|
||||
Где `<version>` - текущая версия миграции (обычно 1 для baseline).
|
||||
|
||||
### Ошибка "no change"
|
||||
|
||||
Если при применении миграций вы видите "no change", это нормально - база данных уже на актуальной версии.
|
||||
|
||||
### Проблемы с путями к миграциям
|
||||
|
||||
Убедитесь, что путь к миграциям правильный:
|
||||
- Локально: `./play-life-backend/migrations`
|
||||
- В Docker: `/migrations`
|
||||
|
||||
Приложение автоматически проверяет оба пути.
|
||||
458
play-life-backend/MIGRATION_RISKS_AND_SOLUTIONS.md
Normal file
458
play-life-backend/MIGRATION_RISKS_AND_SOLUTIONS.md
Normal file
@@ -0,0 +1,458 @@
|
||||
# Анализ рисков миграции на golang-migrate с baseline
|
||||
|
||||
## Критические риски
|
||||
|
||||
### 1. Потеря данных при неправильном применении baseline
|
||||
|
||||
**Риск**: При применении baseline миграции на существующую БД может произойти:
|
||||
- Попытка создать уже существующие таблицы (ошибка)
|
||||
- Потеря данных при DROP/CREATE операциях
|
||||
- Конфликты с существующими данными
|
||||
|
||||
**Вероятность**: Средняя
|
||||
**Влияние**: Критическое
|
||||
|
||||
**Решения**:
|
||||
|
||||
1. **Обязательный backup перед применением**
|
||||
```bash
|
||||
# Создать backup перед миграцией
|
||||
./dump-db.sh --env-file .env baseline-migration-backup
|
||||
```
|
||||
|
||||
2. **Использование `migrate force` вместо `migrate up` для существующих БД**
|
||||
```bash
|
||||
# Для существующих БД - установить версию без применения
|
||||
migrate -path ./migrations -database "postgres://..." force 1
|
||||
```
|
||||
|
||||
3. **Проверка существования таблиц в baseline миграции**
|
||||
- Использовать `CREATE TABLE IF NOT EXISTS` (но это не идеально для baseline)
|
||||
- Или создать скрипт проверки перед применением
|
||||
|
||||
4. **Тестирование на dev окружении**
|
||||
- Сначала применить на dev БД
|
||||
- Проверить целостность данных
|
||||
- Только потом применять на production
|
||||
|
||||
---
|
||||
|
||||
### 2. Ошибки в baseline миграции (неполная схема)
|
||||
|
||||
**Риск**: Baseline миграция может не включать:
|
||||
- Некоторые таблицы или колонки
|
||||
- Индексы или constraints
|
||||
- Materialized views
|
||||
- Начальные данные (словарь с id=0)
|
||||
- Sequences с правильными значениями
|
||||
|
||||
**Вероятность**: Высокая
|
||||
**Влияние**: Критическое
|
||||
|
||||
**Решения**:
|
||||
|
||||
1. **Автоматическая проверка полноты схемы**
|
||||
```bash
|
||||
# Создать скрипт для сравнения текущей схемы с baseline
|
||||
# Использовать pg_dump --schema-only для сравнения
|
||||
pg_dump --schema-only -h $DB_HOST -U $DB_USER -d $DB_NAME > current_schema.sql
|
||||
# Сравнить с baseline миграцией
|
||||
```
|
||||
|
||||
2. **Пошаговая сборка baseline**
|
||||
- Собрать схему из всех init*DB функций
|
||||
- Добавить все миграции 012-029
|
||||
- Проверить через `pg_dump --schema-only` на актуальной БД
|
||||
|
||||
3. **Тестирование baseline на чистой БД**
|
||||
```bash
|
||||
# Создать тестовую БД
|
||||
createdb test_baseline
|
||||
# Применить baseline
|
||||
migrate -path ./migrations -database "postgres://.../test_baseline" up
|
||||
# Сравнить схему с production
|
||||
```
|
||||
|
||||
4. **Валидация через SQL проверки**
|
||||
- Добавить в baseline проверки существования всех таблиц
|
||||
- Использовать `DO $$ BEGIN ... END $$;` блоки для валидации
|
||||
|
||||
---
|
||||
|
||||
### 3. Проблемы с sequences и начальными данными
|
||||
|
||||
**Риск**:
|
||||
- Sequences могут быть не синхронизированы
|
||||
- Начальные данные (словарь id=0) могут конфликтовать
|
||||
- Автоинкременты могут начаться с неправильного значения
|
||||
|
||||
**Вероятность**: Средняя
|
||||
**Влияние**: Среднее
|
||||
|
||||
**Решения**:
|
||||
|
||||
1. **Правильная настройка sequences в baseline**
|
||||
```sql
|
||||
-- После создания таблицы и вставки данных
|
||||
SELECT setval('dictionaries_id_seq',
|
||||
(SELECT MAX(id) FROM dictionaries),
|
||||
true);
|
||||
```
|
||||
|
||||
2. **Использование ON CONFLICT для начальных данных**
|
||||
```sql
|
||||
INSERT INTO dictionaries (id, name)
|
||||
VALUES (0, 'Все слова')
|
||||
ON CONFLICT (id) DO NOTHING;
|
||||
```
|
||||
|
||||
3. **Проверка sequences после baseline**
|
||||
```sql
|
||||
-- Скрипт для проверки всех sequences
|
||||
SELECT schemaname, sequencename, last_value
|
||||
FROM pg_sequences;
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 4. Проблемы с materialized views
|
||||
|
||||
**Риск**:
|
||||
- Materialized view может не создаться корректно
|
||||
- Зависимости от таблиц могут быть нарушены
|
||||
- Данные в MV могут быть неактуальными
|
||||
|
||||
**Вероятность**: Средняя
|
||||
**Влияние**: Среднее
|
||||
|
||||
**Решения**:
|
||||
|
||||
1. **Создание MV после всех таблиц**
|
||||
- Убедиться, что все таблицы созданы до создания MV
|
||||
- Использовать `DROP MATERIALIZED VIEW IF EXISTS` перед созданием
|
||||
|
||||
2. **Обновление данных после создания**
|
||||
```sql
|
||||
-- После создания MV
|
||||
REFRESH MATERIALIZED VIEW weekly_report_mv;
|
||||
```
|
||||
|
||||
3. **Проверка зависимостей**
|
||||
```sql
|
||||
-- Проверить зависимости MV
|
||||
SELECT * FROM pg_depend
|
||||
WHERE objid = 'weekly_report_mv'::regclass;
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 5. Конфликты версий миграций
|
||||
|
||||
**Риск**:
|
||||
- Таблица `schema_migrations` может быть в неправильном состоянии
|
||||
- Версия может быть установлена неправильно
|
||||
- Конфликт между старой и новой системой миграций
|
||||
|
||||
**Вероятность**: Средняя
|
||||
**Влияние**: Высокое
|
||||
|
||||
**Решения**:
|
||||
|
||||
1. **Проверка состояния schema_migrations перед применением**
|
||||
```go
|
||||
// Проверить, существует ли таблица schema_migrations
|
||||
// Если да - проверить текущую версию
|
||||
var version uint
|
||||
err := db.QueryRow("SELECT version FROM schema_migrations LIMIT 1").Scan(&version)
|
||||
```
|
||||
|
||||
2. **Очистка старой таблицы (если была)**
|
||||
```sql
|
||||
-- Если была старая таблица миграций
|
||||
DROP TABLE IF EXISTS old_migrations_table;
|
||||
```
|
||||
|
||||
3. **Использование `migrate force` только для существующих БД**
|
||||
- Новые БД должны использовать `migrate up`
|
||||
- Существующие БД - `migrate force 1`
|
||||
|
||||
---
|
||||
|
||||
### 6. Проблемы с окружениями (dev/prod различия)
|
||||
|
||||
**Риск**:
|
||||
- Различия в схемах между dev и prod
|
||||
- Разные версии PostgreSQL
|
||||
- Разные настройки БД
|
||||
|
||||
**Вероятность**: Средняя
|
||||
**Влияние**: Высокое
|
||||
|
||||
**Решения**:
|
||||
|
||||
1. **Проверка версии PostgreSQL**
|
||||
```sql
|
||||
SELECT version();
|
||||
```
|
||||
|
||||
2. **Тестирование на всех окружениях**
|
||||
- Dev окружение
|
||||
- Staging (если есть)
|
||||
- Production (после успешного тестирования)
|
||||
|
||||
3. **Документирование различий**
|
||||
- Зафиксировать версию PostgreSQL
|
||||
- Зафиксировать настройки БД
|
||||
|
||||
---
|
||||
|
||||
### 7. Проблемы с откатом (rollback)
|
||||
|
||||
**Риск**:
|
||||
- Baseline миграция не может быть откачена
|
||||
- Ошибки при откате последующих миграций
|
||||
- Потеря данных при откате
|
||||
|
||||
**Вероятность**: Низкая
|
||||
**Влияние**: Высокое
|
||||
|
||||
**Решения**:
|
||||
|
||||
1. **Baseline не откатывается (по дизайну)**
|
||||
- Пустой `000001_baseline.down.sql`
|
||||
- Документировать это ограничение
|
||||
|
||||
2. **Правильные down миграции для новых миграций**
|
||||
- Каждая новая миграция должна иметь корректный down файл
|
||||
- Тестировать откат на dev окружении
|
||||
|
||||
3. **Backup перед откатом**
|
||||
- Всегда создавать backup перед откатом
|
||||
- Особенно на production
|
||||
|
||||
---
|
||||
|
||||
### 8. Проблемы при старте приложения
|
||||
|
||||
**Риск**:
|
||||
- Миграции могут не примениться при старте
|
||||
- Ошибки подключения к БД во время миграций
|
||||
- Таймауты при применении миграций
|
||||
|
||||
**Вероятность**: Средняя
|
||||
**Влияние**: Высокое
|
||||
|
||||
**Решения**:
|
||||
|
||||
1. **Обработка ошибок миграций**
|
||||
```go
|
||||
m, err := migrate.NewWithDatabaseInstance(
|
||||
"file://migrations",
|
||||
"postgres", driver)
|
||||
if err != nil {
|
||||
log.Fatal("Failed to initialize migrations:", err)
|
||||
}
|
||||
|
||||
if err := m.Up(); err != nil {
|
||||
if err != migrate.ErrNoChange {
|
||||
log.Fatal("Failed to apply migrations:", err)
|
||||
}
|
||||
log.Println("Database is up to date")
|
||||
}
|
||||
```
|
||||
|
||||
2. **Retry логика для подключения к БД**
|
||||
- Уже есть в коде (10 попыток)
|
||||
- Применить перед миграциями
|
||||
|
||||
3. **Таймауты для миграций**
|
||||
```go
|
||||
// Установить таймаут для миграций
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Minute)
|
||||
defer cancel()
|
||||
```
|
||||
|
||||
4. **Логирование процесса миграций**
|
||||
- Логировать каждую применяемую миграцию
|
||||
- Логировать ошибки с деталями
|
||||
|
||||
---
|
||||
|
||||
### 9. Проблемы с Docker и путями к миграциям
|
||||
|
||||
**Риск**:
|
||||
- Миграции могут не найтись в контейнере
|
||||
- Неправильные пути к файлам миграций
|
||||
- Проблемы с правами доступа
|
||||
|
||||
**Вероятность**: Низкая
|
||||
**Влияние**: Среднее
|
||||
|
||||
**Решения**:
|
||||
|
||||
1. **Проверка путей в Dockerfile**
|
||||
```dockerfile
|
||||
# Убедиться, что миграции копируются
|
||||
COPY play-life-backend/migrations /migrations
|
||||
```
|
||||
|
||||
2. **Использование абсолютных путей**
|
||||
```go
|
||||
migrationsPath := "/migrations"
|
||||
if _, err := os.Stat(migrationsPath); os.IsNotExist(err) {
|
||||
// Fallback для локальной разработки
|
||||
migrationsPath = "play-life-backend/migrations"
|
||||
}
|
||||
```
|
||||
|
||||
3. **Проверка доступности миграций при старте**
|
||||
```go
|
||||
// Проверить, что папка миграций существует
|
||||
if _, err := os.Stat(migrationsPath); os.IsNotExist(err) {
|
||||
log.Fatal("Migrations directory not found:", migrationsPath)
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 10. Проблемы с параллельным доступом
|
||||
|
||||
**Риск**:
|
||||
- Несколько инстансов приложения могут пытаться применить миграции одновременно
|
||||
- Конфликты при применении миграций
|
||||
|
||||
**Вероятность**: Низкая
|
||||
**Влияние**: Высокое
|
||||
|
||||
**Решения**:
|
||||
|
||||
1. **Блокировки на уровне БД**
|
||||
- golang-migrate использует транзакции
|
||||
- PostgreSQL блокирует таблицу schema_migrations
|
||||
|
||||
2. **Применение миграций только в одном инстансе**
|
||||
- Использовать флаг `--migrate` для запуска миграций
|
||||
- Или применять миграции отдельным процессом
|
||||
|
||||
3. **Проверка версии перед применением**
|
||||
```go
|
||||
version, dirty, err := m.Version()
|
||||
if dirty {
|
||||
log.Fatal("Database is in dirty state, manual intervention required")
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## План митигации рисков
|
||||
|
||||
### Этап 1: Подготовка (до применения baseline)
|
||||
|
||||
1. ✅ Создать backup всех БД (dev, staging, prod)
|
||||
2. ✅ Собрать полную схему через `pg_dump --schema-only`
|
||||
3. ✅ Создать baseline миграцию на основе схемы
|
||||
4. ✅ Протестировать baseline на чистой БД
|
||||
5. ✅ Сравнить схему после baseline с текущей схемой
|
||||
|
||||
### Этап 2: Тестирование (на dev окружении)
|
||||
|
||||
1. ✅ Применить baseline через `migrate force 1`
|
||||
2. ✅ Проверить целостность данных
|
||||
3. ✅ Проверить работу приложения
|
||||
4. ✅ Проверить sequences и начальные данные
|
||||
5. ✅ Проверить materialized views
|
||||
|
||||
### Этап 3: Применение (на production)
|
||||
|
||||
1. ✅ Создать backup production БД
|
||||
2. ✅ Применить baseline через `migrate force 1`
|
||||
3. ✅ Проверить работу приложения
|
||||
4. ✅ Мониторинг в течение первых часов
|
||||
|
||||
### Этап 4: Мониторинг (после применения)
|
||||
|
||||
1. ✅ Проверить логи приложения
|
||||
2. ✅ Проверить ошибки БД
|
||||
3. ✅ Проверить производительность
|
||||
4. ✅ Собрать обратную связь от пользователей
|
||||
|
||||
---
|
||||
|
||||
## Чеклист перед применением baseline
|
||||
|
||||
- [ ] Backup всех БД создан и проверен
|
||||
- [ ] Baseline миграция протестирована на чистой БД
|
||||
- [ ] Схема после baseline совпадает с текущей схемой
|
||||
- [ ] Тестирование на dev окружении успешно
|
||||
- [ ] Инструкции по применению baseline готовы
|
||||
- [ ] Команда проинформирована о миграции
|
||||
- [ ] Окно для миграции запланировано (для production)
|
||||
- [ ] План отката подготовлен (если что-то пойдет не так)
|
||||
|
||||
---
|
||||
|
||||
## Скрипты для проверки
|
||||
|
||||
### Скрипт проверки схемы
|
||||
|
||||
```bash
|
||||
#!/bin/bash
|
||||
# check_schema.sh - Проверка полноты baseline миграции
|
||||
|
||||
DB_HOST=${DB_HOST:-localhost}
|
||||
DB_PORT=${DB_PORT:-5432}
|
||||
DB_USER=${DB_USER:-playeng}
|
||||
DB_PASSWORD=${DB_PASSWORD:-playeng}
|
||||
DB_NAME=${DB_NAME:-playeng}
|
||||
|
||||
echo "Проверка схемы БД..."
|
||||
|
||||
# Получить список всех таблиц
|
||||
PGPASSWORD="$DB_PASSWORD" psql -h "$DB_HOST" -p "$DB_PORT" -U "$DB_USER" -d "$DB_NAME" -t -c "
|
||||
SELECT tablename
|
||||
FROM pg_tables
|
||||
WHERE schemaname = 'public'
|
||||
ORDER BY tablename;
|
||||
" > current_tables.txt
|
||||
|
||||
echo "Таблицы в БД сохранены в current_tables.txt"
|
||||
echo "Сравните с таблицами в baseline миграции"
|
||||
```
|
||||
|
||||
### Скрипт применения baseline
|
||||
|
||||
```bash
|
||||
#!/bin/bash
|
||||
# apply_baseline.sh - Безопасное применение baseline
|
||||
|
||||
set -e
|
||||
|
||||
DB_HOST=${DB_HOST:-localhost}
|
||||
DB_PORT=${DB_PORT:-5432}
|
||||
DB_USER=${DB_USER:-playeng}
|
||||
DB_PASSWORD=${DB_PASSWORD:-playeng}
|
||||
DB_NAME=${DB_NAME:-playeng}
|
||||
|
||||
DATABASE_URL="postgres://$DB_USER:$DB_PASSWORD@$DB_HOST:$DB_PORT/$DB_NAME?sslmode=disable"
|
||||
|
||||
echo "⚠️ ВНИМАНИЕ: Это применит baseline миграцию!"
|
||||
echo "База данных: $DB_NAME"
|
||||
echo "Хост: $DB_HOST:$DB_PORT"
|
||||
read -p "Вы уверены? (yes/no): " confirm
|
||||
|
||||
if [ "$confirm" != "yes" ]; then
|
||||
echo "Отменено"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Создать backup
|
||||
echo "Создание backup..."
|
||||
./dump-db.sh --env-file .env baseline-backup-$(date +%Y%m%d_%H%M%S)
|
||||
|
||||
# Применить baseline
|
||||
echo "Применение baseline..."
|
||||
migrate -path ./play-life-backend/migrations -database "$DATABASE_URL" force 1
|
||||
|
||||
echo "✅ Baseline применен успешно"
|
||||
echo "Проверьте работу приложения"
|
||||
```
|
||||
168
play-life-backend/apply_baseline.sh
Executable file
168
play-life-backend/apply_baseline.sh
Executable file
@@ -0,0 +1,168 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Безопасный скрипт для применения baseline миграции к существующим БД
|
||||
# Включает создание backup, проверки и применение baseline
|
||||
|
||||
set -e
|
||||
|
||||
# Цвета для вывода
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
NC='\033[0m' # No Color
|
||||
|
||||
# Получаем переменные окружения
|
||||
DB_HOST=${DB_HOST:-localhost}
|
||||
DB_PORT=${DB_PORT:-5432}
|
||||
DB_USER=${DB_USER:-playeng}
|
||||
DB_PASSWORD=${DB_PASSWORD:-playeng}
|
||||
DB_NAME=${DB_NAME:-playeng}
|
||||
|
||||
MIGRATIONS_PATH="play-life-backend/migrations"
|
||||
BACKUP_DIR="../database-dumps"
|
||||
|
||||
echo "=== Применение baseline миграции ==="
|
||||
echo ""
|
||||
|
||||
# Проверяем наличие необходимых инструментов
|
||||
if ! command -v migrate &> /dev/null; then
|
||||
echo -e "${RED}Ошибка: migrate не найден. Установите golang-migrate:${NC}"
|
||||
echo " brew install golang-migrate"
|
||||
echo " или"
|
||||
echo " go install -tags 'postgres' github.com/golang-migrate/migrate/v4/cmd/migrate@latest"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if ! command -v pg_dump &> /dev/null; then
|
||||
echo -e "${RED}Ошибка: pg_dump не найден. Установите PostgreSQL client tools.${NC}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Проверяем наличие директории миграций
|
||||
if [ ! -d "$MIGRATIONS_PATH" ]; then
|
||||
echo -e "${RED}Ошибка: Директория миграций не найдена: $MIGRATIONS_PATH${NC}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Проверяем наличие baseline миграции
|
||||
if [ ! -f "$MIGRATIONS_PATH/000001_baseline.up.sql" ]; then
|
||||
echo -e "${RED}Ошибка: Baseline миграция не найдена: $MIGRATIONS_PATH/000001_baseline.up.sql${NC}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "Параметры подключения:"
|
||||
echo " Host: $DB_HOST"
|
||||
echo " Port: $DB_PORT"
|
||||
echo " User: $DB_USER"
|
||||
echo " Database: $DB_NAME"
|
||||
echo ""
|
||||
|
||||
# Проверяем подключение к БД
|
||||
echo "1. Проверка подключения к БД..."
|
||||
PGPASSWORD=$DB_PASSWORD psql \
|
||||
-h $DB_HOST \
|
||||
-p $DB_PORT \
|
||||
-U $DB_USER \
|
||||
-d $DB_NAME \
|
||||
-c "SELECT 1;" > /dev/null 2>&1
|
||||
|
||||
if [ $? -ne 0 ]; then
|
||||
echo -e "${RED}Ошибка: Не удалось подключиться к БД${NC}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo -e "${GREEN}✓ Подключение успешно${NC}"
|
||||
echo ""
|
||||
|
||||
# Проверяем текущую версию миграции
|
||||
echo "2. Проверка текущей версии миграции..."
|
||||
DATABASE_URL="postgres://$DB_USER:$DB_PASSWORD@$DB_HOST:$DB_PORT/$DB_NAME?sslmode=disable"
|
||||
|
||||
CURRENT_VERSION=$(migrate -path "$MIGRATIONS_PATH" -database "$DATABASE_URL" version 2>&1 || echo "none")
|
||||
|
||||
if echo "$CURRENT_VERSION" | grep -q "dirty"; then
|
||||
echo -e "${YELLOW}⚠ База данных находится в состоянии 'dirty'${NC}"
|
||||
echo " Это нормально для baseline - будет исправлено"
|
||||
elif echo "$CURRENT_VERSION" | grep -q "^[0-9]"; then
|
||||
VERSION_NUM=$(echo "$CURRENT_VERSION" | grep -oE "^[0-9]+" || echo "0")
|
||||
if [ "$VERSION_NUM" -ge 1 ]; then
|
||||
echo -e "${GREEN}✓ Версия миграции уже установлена: $VERSION_NUM${NC}"
|
||||
echo " Baseline уже применен, дальнейшие действия не требуются"
|
||||
exit 0
|
||||
fi
|
||||
fi
|
||||
|
||||
echo " Текущая версия: $CURRENT_VERSION"
|
||||
echo ""
|
||||
|
||||
# Создаем backup
|
||||
echo "3. Создание backup БД..."
|
||||
mkdir -p "$BACKUP_DIR"
|
||||
BACKUP_FILE="$BACKUP_DIR/baseline_backup_$(date +%Y%m%d_%H%M%S).sql.gz"
|
||||
|
||||
PGPASSWORD=$DB_PASSWORD pg_dump \
|
||||
-h $DB_HOST \
|
||||
-p $DB_PORT \
|
||||
-U $DB_USER \
|
||||
-d $DB_NAME \
|
||||
| gzip > "$BACKUP_FILE"
|
||||
|
||||
if [ $? -ne 0 ]; then
|
||||
echo -e "${RED}Ошибка: Не удалось создать backup${NC}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
BACKUP_SIZE=$(du -h "$BACKUP_FILE" | cut -f1)
|
||||
echo -e "${GREEN}✓ Backup создан: $BACKUP_FILE (размер: $BACKUP_SIZE)${NC}"
|
||||
echo ""
|
||||
|
||||
# Подтверждение
|
||||
echo "4. Подтверждение применения baseline..."
|
||||
echo ""
|
||||
echo -e "${YELLOW}ВНИМАНИЕ:${NC}"
|
||||
echo " Будет установлена версия миграции в 1 (baseline)"
|
||||
echo " Сама миграция НЕ будет применена (схема уже существует)"
|
||||
echo " Backup сохранен в: $BACKUP_FILE"
|
||||
echo ""
|
||||
read -p "Продолжить? (yes/no): " CONFIRM
|
||||
|
||||
if [ "$CONFIRM" != "yes" ]; then
|
||||
echo "Отменено пользователем"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Применяем baseline (force 1)
|
||||
echo ""
|
||||
echo "5. Установка версии миграции в 1 (baseline)..."
|
||||
migrate -path "$MIGRATIONS_PATH" \
|
||||
-database "$DATABASE_URL" \
|
||||
force 1
|
||||
|
||||
if [ $? -ne 0 ]; then
|
||||
echo -e "${RED}Ошибка: Не удалось установить версию миграции${NC}"
|
||||
echo " Backup доступен в: $BACKUP_FILE"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo -e "${GREEN}✓ Версия миграции установлена${NC}"
|
||||
echo ""
|
||||
|
||||
# Проверяем результат
|
||||
echo "6. Проверка результата..."
|
||||
FINAL_VERSION=$(migrate -path "$MIGRATIONS_PATH" -database "$DATABASE_URL" version 2>&1)
|
||||
echo " Версия миграции: $FINAL_VERSION"
|
||||
|
||||
if echo "$FINAL_VERSION" | grep -qE "^1"; then
|
||||
echo -e "${GREEN}✓ Baseline успешно применен!${NC}"
|
||||
else
|
||||
echo -e "${YELLOW}⚠ Версия миграции: $FINAL_VERSION${NC}"
|
||||
echo " Это может быть нормально, если база в состоянии 'dirty'"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "=== Готово ==="
|
||||
echo ""
|
||||
echo "Backup сохранен в: $BACKUP_FILE"
|
||||
echo "Версия миграции установлена в: 1 (baseline)"
|
||||
echo ""
|
||||
echo "Теперь приложение будет автоматически применять новые миграции при запуске."
|
||||
@@ -1,17 +1,18 @@
|
||||
module play-eng-backend
|
||||
|
||||
go 1.24
|
||||
go 1.24.0
|
||||
|
||||
require (
|
||||
github.com/chromedp/chromedp v0.14.2
|
||||
github.com/disintegration/imaging v1.6.2
|
||||
github.com/go-telegram-bot-api/telegram-bot-api/v5 v5.5.1
|
||||
github.com/golang-jwt/jwt/v5 v5.3.0
|
||||
github.com/golang-migrate/migrate/v4 v4.19.1
|
||||
github.com/gorilla/mux v1.8.1
|
||||
github.com/joho/godotenv v1.5.1
|
||||
github.com/lib/pq v1.10.9
|
||||
github.com/robfig/cron/v3 v3.0.1
|
||||
golang.org/x/crypto v0.28.0
|
||||
golang.org/x/crypto v0.45.0
|
||||
)
|
||||
|
||||
require (
|
||||
@@ -22,5 +23,5 @@ require (
|
||||
github.com/gobwas/pool v0.2.1 // indirect
|
||||
github.com/gobwas/ws v1.4.0 // indirect
|
||||
golang.org/x/image v0.0.0-20191009234506-e7c1f5e7dbb8 // indirect
|
||||
golang.org/x/sys v0.34.0 // indirect
|
||||
golang.org/x/sys v0.38.0 // indirect
|
||||
)
|
||||
|
||||
@@ -1,13 +1,39 @@
|
||||
github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 h1:L/gRVlceqvL25UVaW/CKtUDjefjrs0SPonmDGUVOYP0=
|
||||
github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E=
|
||||
github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY=
|
||||
github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU=
|
||||
github.com/chromedp/cdproto v0.0.0-20250724212937-08a3db8b4327 h1:UQ4AU+BGti3Sy/aLU8KVseYKNALcX9UXY6DfpwQ6J8E=
|
||||
github.com/chromedp/cdproto v0.0.0-20250724212937-08a3db8b4327/go.mod h1:NItd7aLkcfOA/dcMXvl8p1u+lQqioRMq/SqDp71Pb/k=
|
||||
github.com/chromedp/chromedp v0.14.2 h1:r3b/WtwM50RsBZHMUm9fsNhhzRStTHrKdr2zmwbZSzM=
|
||||
github.com/chromedp/chromedp v0.14.2/go.mod h1:rHzAv60xDE7VNy/MYtTUrYreSc0ujt2O1/C3bzctYBo=
|
||||
github.com/chromedp/sysutil v1.1.0 h1:PUFNv5EcprjqXZD9nJb9b/c9ibAbxiYo4exNWZyipwM=
|
||||
github.com/chromedp/sysutil v1.1.0/go.mod h1:WiThHUdltqCNKGc4gaU50XgYjwjYIhKWoHGPTUfWTJ8=
|
||||
github.com/containerd/errdefs v1.0.0 h1:tg5yIfIlQIrxYtu9ajqY42W3lpS19XqdxRQeEwYG8PI=
|
||||
github.com/containerd/errdefs v1.0.0/go.mod h1:+YBYIdtsnF4Iw6nWZhJcqGSg/dwvV7tyJ/kCkyJ2k+M=
|
||||
github.com/containerd/errdefs/pkg v0.3.0 h1:9IKJ06FvyNlexW690DXuQNx2KA2cUJXx151Xdx3ZPPE=
|
||||
github.com/containerd/errdefs/pkg v0.3.0/go.mod h1:NJw6s9HwNuRhnjJhM7pylWwMyAkmCQvQ4GpJHEqRLVk=
|
||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
|
||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/dhui/dktest v0.4.6 h1:+DPKyScKSEp3VLtbMDHcUq6V5Lm5zfZZVb0Sk7Ahom4=
|
||||
github.com/dhui/dktest v0.4.6/go.mod h1:JHTSYDtKkvFNFHJKqCzVzqXecyv+tKt8EzceOmQOgbU=
|
||||
github.com/disintegration/imaging v1.6.2 h1:w1LecBlG2Lnp8B3jk5zSuNqd7b4DXhcjwek1ei82L+c=
|
||||
github.com/disintegration/imaging v1.6.2/go.mod h1:44/5580QXChDfwIclfc/PCwrr44amcmDAg8hxG0Ewe4=
|
||||
github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk=
|
||||
github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E=
|
||||
github.com/docker/docker v28.3.3+incompatible h1:Dypm25kh4rmk49v1eiVbsAtpAsYURjYkaKubwuBdxEI=
|
||||
github.com/docker/docker v28.3.3+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
|
||||
github.com/docker/go-connections v0.5.0 h1:USnMq7hx7gwdVZq1L49hLXaFtUdTADjXGp+uj1Br63c=
|
||||
github.com/docker/go-connections v0.5.0/go.mod h1:ov60Kzw0kKElRwhNs9UlUHAE/F9Fe6GLaXnqyDdmEXc=
|
||||
github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4=
|
||||
github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
|
||||
github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=
|
||||
github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
|
||||
github.com/go-json-experiment/json v0.0.0-20250725192818-e39067aee2d2 h1:iizUGZ9pEquQS5jTGkh4AqeeHCMbfbjeb0zMt0aEFzs=
|
||||
github.com/go-json-experiment/json v0.0.0-20250725192818-e39067aee2d2/go.mod h1:TiCD2a1pcmjd7YnhGH0f/zKNcCD06B029pHhzV23c2M=
|
||||
github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
|
||||
github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
||||
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
|
||||
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
|
||||
github.com/go-telegram-bot-api/telegram-bot-api/v5 v5.5.1 h1:wG8n/XJQ07TmjbITcGiUaOtXxdrINDz1b0J1w0SzqDc=
|
||||
github.com/go-telegram-bot-api/telegram-bot-api/v5 v5.5.1/go.mod h1:A2S0CWkNylc2phvKXWBBdD3K0iGnDBGbzRpISP2zBl8=
|
||||
github.com/gobwas/httphead v0.1.0 h1:exrUm0f4YX0L7EBwZHuCF4GDp8aJfVeBrlLQrs6NqWU=
|
||||
@@ -16,8 +42,12 @@ github.com/gobwas/pool v0.2.1 h1:xfeeEhW7pwmX8nuLVlqbzVc7udMDrwetjEv+TZIz1og=
|
||||
github.com/gobwas/pool v0.2.1/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw=
|
||||
github.com/gobwas/ws v1.4.0 h1:CTaoG1tojrh4ucGPcoJFiAQUAsEWekEWvLy7GsVNqGs=
|
||||
github.com/gobwas/ws v1.4.0/go.mod h1:G3gNqMNtPppf5XUz7O4shetPpcZ1VJ7zt18dlUeakrc=
|
||||
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
|
||||
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
|
||||
github.com/golang-jwt/jwt/v5 v5.3.0 h1:pv4AsKCKKZuqlgs5sUmn4x8UlGa0kEVt/puTpKx9vvo=
|
||||
github.com/golang-jwt/jwt/v5 v5.3.0/go.mod h1:fxCRLWMO43lRc8nhHWY6LGqRcf+1gQWArsqaEUEa5bE=
|
||||
github.com/golang-migrate/migrate/v4 v4.19.1 h1:OCyb44lFuQfYXYLx1SCxPZQGU7mcaZ7gH9yH4jSFbBA=
|
||||
github.com/golang-migrate/migrate/v4 v4.19.1/go.mod h1:CTcgfjxhaUtsLipnLoQRWCrjYXycRz/g5+RWDuYgPrE=
|
||||
github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY=
|
||||
github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ=
|
||||
github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
|
||||
@@ -26,15 +56,43 @@ github.com/ledongthuc/pdf v0.0.0-20220302134840-0c2507a12d80 h1:6Yzfa6GP0rIo/kUL
|
||||
github.com/ledongthuc/pdf v0.0.0-20220302134840-0c2507a12d80/go.mod h1:imJHygn/1yfhB7XSJJKlFZKl/J+dCPAknuiaGOshXAs=
|
||||
github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw=
|
||||
github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
|
||||
github.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3Nl2EsFP0=
|
||||
github.com/moby/docker-image-spec v1.3.1/go.mod h1:eKmb5VW8vQEh/BAr2yvVNvuiJuY6UIocYsFu/DxxRpo=
|
||||
github.com/moby/term v0.5.0 h1:xt8Q1nalod/v7BqbG21f8mQPqH+xAaC9C3N3wfWbVP0=
|
||||
github.com/moby/term v0.5.0/go.mod h1:8FzsFHVUBGZdbDsJw/ot+X+d5HLUbvklYLJ9uGfcI3Y=
|
||||
github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A=
|
||||
github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc=
|
||||
github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U=
|
||||
github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM=
|
||||
github.com/opencontainers/image-spec v1.1.0 h1:8SG7/vwALn54lVB/0yZ/MMwhFrPYtpEHQb2IpWsCzug=
|
||||
github.com/opencontainers/image-spec v1.1.0/go.mod h1:W4s4sFTMaBeK1BQLXbG4AdM2szdn85PY75RI83NrTrM=
|
||||
github.com/orisano/pixelmatch v0.0.0-20220722002657-fb0b55479cde h1:x0TT0RDC7UhAVbbWWBzr41ElhJx5tXPWkIHA2HWPRuw=
|
||||
github.com/orisano/pixelmatch v0.0.0-20220722002657-fb0b55479cde/go.mod h1:nZgzbfBr3hhjoZnS66nKrHmduYNpc34ny7RK4z5/HM0=
|
||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
|
||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs=
|
||||
github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro=
|
||||
golang.org/x/crypto v0.28.0 h1:GBDwsMXVQi34v5CCYUm2jkJvu4cbtru2U4TN2PSyQnw=
|
||||
golang.org/x/crypto v0.28.0/go.mod h1:rmgy+3RHxRZMyY0jjAJShp2zgEdOqj2AO7U0pYmeQ7U=
|
||||
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
|
||||
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||
go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA=
|
||||
go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A=
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0 h1:F7Jx+6hwnZ41NSFTO5q4LYDtJRXBf2PD0rNBkeB/lus=
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0/go.mod h1:UHB22Z8QsdRDrnAtX4PntOl36ajSxcdUMt1sF7Y6E7Q=
|
||||
go.opentelemetry.io/otel v1.37.0 h1:9zhNfelUvx0KBfu/gb+ZgeAfAgtWrfHJZcAqFC228wQ=
|
||||
go.opentelemetry.io/otel v1.37.0/go.mod h1:ehE/umFRLnuLa/vSccNq9oS1ErUlkkK71gMcN34UG8I=
|
||||
go.opentelemetry.io/otel/metric v1.37.0 h1:mvwbQS5m0tbmqML4NqK+e3aDiO02vsf/WgbsdpcPoZE=
|
||||
go.opentelemetry.io/otel/metric v1.37.0/go.mod h1:04wGrZurHYKOc+RKeye86GwKiTb9FKm1WHtO+4EVr2E=
|
||||
go.opentelemetry.io/otel/trace v1.37.0 h1:HLdcFNbRQBE2imdSEgm/kwqmQj1Or1l/7bW6mxVK7z4=
|
||||
go.opentelemetry.io/otel/trace v1.37.0/go.mod h1:TlgrlQ+PtQO5XFerSPUYG0JSgGyryXewPGyayAWSBS0=
|
||||
golang.org/x/crypto v0.45.0 h1:jMBrvKuj23MTlT0bQEOBcAE0mjg8mK9RXFhRH6nyF3Q=
|
||||
golang.org/x/crypto v0.45.0/go.mod h1:XTGrrkGJve7CYK7J8PEww4aY7gM3qMCElcJQ8n8JdX4=
|
||||
golang.org/x/image v0.0.0-20191009234506-e7c1f5e7dbb8 h1:hVwzHzIUGRjiF7EcUjqNxk3NCfkPxbDKRdnNE1Rpg0U=
|
||||
golang.org/x/image v0.0.0-20191009234506-e7c1f5e7dbb8/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.34.0 h1:H5Y5sJ2L2JRdyv7ROF1he/lPdvFsd0mJHFw2ThKHxLA=
|
||||
golang.org/x/sys v0.34.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
||||
golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc=
|
||||
golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
3
play-life-backend/migrations/000001_baseline.down.sql
Normal file
3
play-life-backend/migrations/000001_baseline.down.sql
Normal file
@@ -0,0 +1,3 @@
|
||||
-- Baseline migration cannot be rolled back
|
||||
-- This is the initial state of the database schema
|
||||
-- If you need to revert, you must manually drop all tables and recreate from scratch
|
||||
497
play-life-backend/migrations/000001_baseline.up.sql
Normal file
497
play-life-backend/migrations/000001_baseline.up.sql
Normal file
@@ -0,0 +1,497 @@
|
||||
-- Baseline Migration: Complete database schema
|
||||
-- This migration represents the current state of the database schema
|
||||
-- For existing databases, use: migrate force 1 (do not run this migration)
|
||||
-- For new databases, this will create the complete schema
|
||||
|
||||
-- ============================================
|
||||
-- Core Tables (no dependencies)
|
||||
-- ============================================
|
||||
|
||||
-- Users table (base for multi-tenancy)
|
||||
CREATE TABLE users (
|
||||
id SERIAL PRIMARY KEY,
|
||||
email VARCHAR(255) NOT NULL UNIQUE,
|
||||
password_hash VARCHAR(255) NOT NULL,
|
||||
name VARCHAR(255),
|
||||
created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
|
||||
is_active BOOLEAN DEFAULT TRUE,
|
||||
last_login_at TIMESTAMP WITH TIME ZONE
|
||||
);
|
||||
|
||||
CREATE INDEX idx_users_email ON users(email);
|
||||
|
||||
-- Dictionaries table
|
||||
CREATE TABLE dictionaries (
|
||||
id SERIAL PRIMARY KEY,
|
||||
name VARCHAR(255) NOT NULL,
|
||||
user_id INTEGER REFERENCES users(id) ON DELETE CASCADE
|
||||
);
|
||||
|
||||
CREATE INDEX idx_dictionaries_user_id ON dictionaries(user_id);
|
||||
|
||||
-- Insert default dictionary with id = 0
|
||||
DO $$
|
||||
BEGIN
|
||||
-- Set sequence to -1 so next value will be 0
|
||||
PERFORM setval('dictionaries_id_seq', -1, false);
|
||||
|
||||
-- Insert the default dictionary with id = 0
|
||||
INSERT INTO dictionaries (id, name)
|
||||
VALUES (0, 'Все слова')
|
||||
ON CONFLICT (id) DO NOTHING;
|
||||
|
||||
-- Set the sequence to start from 1 (so next auto-increment will be 1)
|
||||
PERFORM setval('dictionaries_id_seq', 1, false);
|
||||
EXCEPTION
|
||||
WHEN others THEN
|
||||
-- If sequence doesn't exist or other error, try without sequence manipulation
|
||||
INSERT INTO dictionaries (id, name)
|
||||
VALUES (0, 'Все слова')
|
||||
ON CONFLICT (id) DO NOTHING;
|
||||
END $$;
|
||||
|
||||
-- Projects table
|
||||
CREATE TABLE projects (
|
||||
id SERIAL PRIMARY KEY,
|
||||
name VARCHAR(255) NOT NULL,
|
||||
priority SMALLINT,
|
||||
deleted BOOLEAN NOT NULL DEFAULT FALSE,
|
||||
user_id INTEGER REFERENCES users(id) ON DELETE CASCADE
|
||||
);
|
||||
|
||||
CREATE INDEX idx_projects_deleted ON projects(deleted);
|
||||
CREATE INDEX idx_projects_user_id ON projects(user_id);
|
||||
|
||||
-- Entries table
|
||||
CREATE TABLE entries (
|
||||
id SERIAL PRIMARY KEY,
|
||||
text TEXT NOT NULL,
|
||||
created_date TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
user_id INTEGER REFERENCES users(id) ON DELETE CASCADE
|
||||
);
|
||||
|
||||
CREATE INDEX idx_entries_user_id ON entries(user_id);
|
||||
|
||||
-- ============================================
|
||||
-- Dependent Tables
|
||||
-- ============================================
|
||||
|
||||
-- Words table (depends on dictionaries, users)
|
||||
CREATE TABLE words (
|
||||
id SERIAL PRIMARY KEY,
|
||||
name VARCHAR(255) NOT NULL,
|
||||
translation TEXT NOT NULL,
|
||||
description TEXT,
|
||||
dictionary_id INTEGER NOT NULL DEFAULT 0 REFERENCES dictionaries(id),
|
||||
user_id INTEGER REFERENCES users(id) ON DELETE CASCADE
|
||||
);
|
||||
|
||||
CREATE INDEX idx_words_dictionary_id ON words(dictionary_id);
|
||||
CREATE INDEX idx_words_user_id ON words(user_id);
|
||||
|
||||
-- Progress table (depends on words, users)
|
||||
CREATE TABLE progress (
|
||||
id SERIAL PRIMARY KEY,
|
||||
word_id INTEGER NOT NULL REFERENCES words(id) ON DELETE CASCADE,
|
||||
success INTEGER DEFAULT 0,
|
||||
failure INTEGER DEFAULT 0,
|
||||
last_success_at TIMESTAMP,
|
||||
last_failure_at TIMESTAMP,
|
||||
user_id INTEGER REFERENCES users(id) ON DELETE CASCADE,
|
||||
CONSTRAINT progress_word_user_unique UNIQUE (word_id, user_id)
|
||||
);
|
||||
|
||||
CREATE INDEX idx_progress_user_id ON progress(user_id);
|
||||
CREATE UNIQUE INDEX idx_progress_word_user_unique ON progress(word_id, user_id);
|
||||
|
||||
-- Configs table (depends on users)
|
||||
CREATE TABLE configs (
|
||||
id SERIAL PRIMARY KEY,
|
||||
words_count INTEGER NOT NULL,
|
||||
max_cards INTEGER,
|
||||
user_id INTEGER REFERENCES users(id) ON DELETE CASCADE
|
||||
);
|
||||
|
||||
CREATE INDEX idx_configs_user_id ON configs(user_id);
|
||||
|
||||
-- Config dictionaries table (depends on configs, dictionaries)
|
||||
CREATE TABLE config_dictionaries (
|
||||
config_id INTEGER NOT NULL REFERENCES configs(id) ON DELETE CASCADE,
|
||||
dictionary_id INTEGER NOT NULL REFERENCES dictionaries(id) ON DELETE CASCADE,
|
||||
PRIMARY KEY (config_id, dictionary_id)
|
||||
);
|
||||
|
||||
CREATE INDEX idx_config_dictionaries_config_id ON config_dictionaries(config_id);
|
||||
CREATE INDEX idx_config_dictionaries_dictionary_id ON config_dictionaries(dictionary_id);
|
||||
|
||||
-- Nodes table (depends on projects, entries, users)
|
||||
CREATE TABLE nodes (
|
||||
id SERIAL PRIMARY KEY,
|
||||
project_id INTEGER NOT NULL REFERENCES projects(id) ON DELETE CASCADE,
|
||||
entry_id INTEGER NOT NULL REFERENCES entries(id) ON DELETE CASCADE,
|
||||
score NUMERIC(8,4),
|
||||
user_id INTEGER REFERENCES users(id) ON DELETE CASCADE
|
||||
);
|
||||
|
||||
CREATE INDEX idx_nodes_project_id ON nodes(project_id);
|
||||
CREATE INDEX idx_nodes_entry_id ON nodes(entry_id);
|
||||
CREATE INDEX idx_nodes_user_id ON nodes(user_id);
|
||||
|
||||
-- Weekly goals table (depends on projects, users)
|
||||
CREATE TABLE weekly_goals (
|
||||
id SERIAL PRIMARY KEY,
|
||||
project_id INTEGER NOT NULL REFERENCES projects(id) ON DELETE CASCADE,
|
||||
goal_year INTEGER NOT NULL,
|
||||
goal_week INTEGER NOT NULL,
|
||||
min_goal_score NUMERIC(10,4) NOT NULL DEFAULT 0,
|
||||
max_goal_score NUMERIC(10,4),
|
||||
max_score NUMERIC(10,4),
|
||||
priority SMALLINT,
|
||||
user_id INTEGER REFERENCES users(id) ON DELETE CASCADE,
|
||||
CONSTRAINT weekly_goals_project_id_goal_year_goal_week_key UNIQUE (project_id, goal_year, goal_week)
|
||||
);
|
||||
|
||||
CREATE INDEX idx_weekly_goals_project_id ON weekly_goals(project_id);
|
||||
CREATE INDEX idx_weekly_goals_user_id ON weekly_goals(user_id);
|
||||
|
||||
-- Tasks table (depends on users)
|
||||
CREATE TABLE tasks (
|
||||
id SERIAL PRIMARY KEY,
|
||||
user_id INTEGER REFERENCES users(id) ON DELETE CASCADE,
|
||||
name VARCHAR(255) NOT NULL,
|
||||
completed INTEGER DEFAULT 0,
|
||||
last_completed_at TIMESTAMP WITH TIME ZONE,
|
||||
parent_task_id INTEGER REFERENCES tasks(id) ON DELETE CASCADE,
|
||||
reward_message TEXT,
|
||||
progression_base NUMERIC(10,4),
|
||||
deleted BOOLEAN DEFAULT FALSE,
|
||||
repetition_period INTERVAL,
|
||||
next_show_at TIMESTAMP WITH TIME ZONE,
|
||||
repetition_date TEXT,
|
||||
config_id INTEGER REFERENCES configs(id) ON DELETE SET NULL,
|
||||
wishlist_id INTEGER,
|
||||
reward_policy VARCHAR(20) DEFAULT 'personal'
|
||||
);
|
||||
|
||||
CREATE INDEX idx_tasks_user_id ON tasks(user_id);
|
||||
CREATE INDEX idx_tasks_parent_task_id ON tasks(parent_task_id);
|
||||
CREATE INDEX idx_tasks_deleted ON tasks(deleted);
|
||||
CREATE INDEX idx_tasks_last_completed_at ON tasks(last_completed_at);
|
||||
CREATE INDEX idx_tasks_config_id ON tasks(config_id);
|
||||
CREATE UNIQUE INDEX idx_tasks_config_id_unique ON tasks(config_id) WHERE config_id IS NOT NULL AND deleted = FALSE;
|
||||
CREATE INDEX idx_tasks_wishlist_id ON tasks(wishlist_id);
|
||||
CREATE UNIQUE INDEX idx_tasks_wishlist_id_unique ON tasks(wishlist_id) WHERE wishlist_id IS NOT NULL AND deleted = FALSE;
|
||||
CREATE INDEX idx_tasks_id_user_deleted ON tasks(id, user_id, deleted) WHERE deleted = FALSE;
|
||||
CREATE INDEX idx_tasks_parent_deleted_covering ON tasks(parent_task_id, deleted, id)
|
||||
INCLUDE (name, completed, last_completed_at, reward_message, progression_base)
|
||||
WHERE deleted = FALSE;
|
||||
|
||||
COMMENT ON COLUMN tasks.config_id IS 'Link to test config. NULL if task is not a test.';
|
||||
COMMENT ON COLUMN tasks.reward_policy IS 'For wishlist tasks: personal = only if user completes, shared = anyone completes';
|
||||
|
||||
-- Reward configs table (depends on tasks, projects)
|
||||
CREATE TABLE reward_configs (
|
||||
id SERIAL PRIMARY KEY,
|
||||
position INTEGER NOT NULL,
|
||||
task_id INTEGER REFERENCES tasks(id) ON DELETE CASCADE,
|
||||
project_id INTEGER REFERENCES projects(id) ON DELETE CASCADE,
|
||||
value NUMERIC(10,4) NOT NULL,
|
||||
use_progression BOOLEAN DEFAULT FALSE
|
||||
);
|
||||
|
||||
CREATE INDEX idx_reward_configs_task_id ON reward_configs(task_id);
|
||||
CREATE INDEX idx_reward_configs_project_id ON reward_configs(project_id);
|
||||
CREATE UNIQUE INDEX idx_reward_configs_task_position ON reward_configs(task_id, position);
|
||||
|
||||
-- Telegram integrations table (depends on users)
|
||||
CREATE TABLE telegram_integrations (
|
||||
id SERIAL PRIMARY KEY,
|
||||
chat_id VARCHAR(255),
|
||||
telegram_user_id BIGINT,
|
||||
start_token VARCHAR(255),
|
||||
user_id INTEGER REFERENCES users(id) ON DELETE CASCADE,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
CREATE UNIQUE INDEX idx_telegram_integrations_user_id_unique ON telegram_integrations(user_id) WHERE user_id IS NOT NULL;
|
||||
CREATE INDEX idx_telegram_integrations_user_id ON telegram_integrations(user_id);
|
||||
CREATE UNIQUE INDEX idx_telegram_integrations_start_token ON telegram_integrations(start_token) WHERE start_token IS NOT NULL;
|
||||
CREATE UNIQUE INDEX idx_telegram_integrations_telegram_user_id ON telegram_integrations(telegram_user_id) WHERE telegram_user_id IS NOT NULL;
|
||||
CREATE INDEX idx_telegram_integrations_chat_id ON telegram_integrations(chat_id) WHERE chat_id IS NOT NULL;
|
||||
|
||||
-- Todoist integrations table (depends on users)
|
||||
CREATE TABLE todoist_integrations (
|
||||
id SERIAL PRIMARY KEY,
|
||||
user_id INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
||||
todoist_user_id BIGINT,
|
||||
todoist_email VARCHAR(255),
|
||||
access_token TEXT,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
CONSTRAINT todoist_integrations_user_id_unique UNIQUE (user_id)
|
||||
);
|
||||
|
||||
CREATE INDEX idx_todoist_integrations_user_id ON todoist_integrations(user_id);
|
||||
CREATE UNIQUE INDEX idx_todoist_integrations_todoist_user_id ON todoist_integrations(todoist_user_id) WHERE todoist_user_id IS NOT NULL;
|
||||
CREATE UNIQUE INDEX idx_todoist_integrations_todoist_email ON todoist_integrations(todoist_email) WHERE todoist_email IS NOT NULL;
|
||||
|
||||
-- Wishlist boards table (depends on users)
|
||||
CREATE TABLE wishlist_boards (
|
||||
id SERIAL PRIMARY KEY,
|
||||
owner_id INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
||||
name VARCHAR(255) NOT NULL,
|
||||
invite_token VARCHAR(64) UNIQUE,
|
||||
invite_enabled BOOLEAN DEFAULT FALSE,
|
||||
created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
|
||||
deleted BOOLEAN DEFAULT FALSE
|
||||
);
|
||||
|
||||
CREATE INDEX idx_wishlist_boards_owner_id ON wishlist_boards(owner_id);
|
||||
CREATE INDEX idx_wishlist_boards_invite_token ON wishlist_boards(invite_token) WHERE invite_token IS NOT NULL;
|
||||
CREATE INDEX idx_wishlist_boards_owner_deleted ON wishlist_boards(owner_id, deleted);
|
||||
|
||||
-- Wishlist board members table (depends on wishlist_boards, users)
|
||||
CREATE TABLE wishlist_board_members (
|
||||
id SERIAL PRIMARY KEY,
|
||||
board_id INTEGER NOT NULL REFERENCES wishlist_boards(id) ON DELETE CASCADE,
|
||||
user_id INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
||||
joined_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
|
||||
CONSTRAINT unique_board_member UNIQUE (board_id, user_id)
|
||||
);
|
||||
|
||||
CREATE INDEX idx_board_members_board_id ON wishlist_board_members(board_id);
|
||||
CREATE INDEX idx_board_members_user_id ON wishlist_board_members(user_id);
|
||||
|
||||
-- Wishlist items table (depends on users, wishlist_boards)
|
||||
CREATE TABLE wishlist_items (
|
||||
id SERIAL PRIMARY KEY,
|
||||
user_id INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
||||
name VARCHAR(255) NOT NULL,
|
||||
price NUMERIC(10,2),
|
||||
image_path VARCHAR(500),
|
||||
link TEXT,
|
||||
completed BOOLEAN DEFAULT FALSE,
|
||||
created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
|
||||
deleted BOOLEAN DEFAULT FALSE,
|
||||
board_id INTEGER REFERENCES wishlist_boards(id) ON DELETE CASCADE,
|
||||
author_id INTEGER REFERENCES users(id) ON DELETE SET NULL
|
||||
);
|
||||
|
||||
CREATE INDEX idx_wishlist_items_user_id ON wishlist_items(user_id);
|
||||
CREATE INDEX idx_wishlist_items_user_deleted ON wishlist_items(user_id, deleted);
|
||||
CREATE INDEX idx_wishlist_items_user_completed ON wishlist_items(user_id, completed, deleted);
|
||||
CREATE INDEX idx_wishlist_items_board_id ON wishlist_items(board_id);
|
||||
CREATE INDEX idx_wishlist_items_author_id ON wishlist_items(author_id);
|
||||
CREATE INDEX idx_wishlist_items_id_deleted_covering ON wishlist_items(id, deleted)
|
||||
INCLUDE (name)
|
||||
WHERE deleted = FALSE;
|
||||
|
||||
-- Add foreign key for tasks.wishlist_id after wishlist_items is created
|
||||
ALTER TABLE tasks ADD CONSTRAINT tasks_wishlist_id_fkey
|
||||
FOREIGN KEY (wishlist_id) REFERENCES wishlist_items(id) ON DELETE SET NULL;
|
||||
|
||||
COMMENT ON TABLE wishlist_items IS 'Wishlist items for users';
|
||||
COMMENT ON COLUMN wishlist_items.completed IS 'Flag indicating item was purchased/received';
|
||||
COMMENT ON COLUMN wishlist_items.image_path IS 'Path to image file relative to uploads root';
|
||||
|
||||
-- Task conditions table (depends on tasks)
|
||||
CREATE TABLE task_conditions (
|
||||
id SERIAL PRIMARY KEY,
|
||||
task_id INTEGER NOT NULL REFERENCES tasks(id) ON DELETE CASCADE,
|
||||
created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
|
||||
CONSTRAINT unique_task_condition UNIQUE (task_id)
|
||||
);
|
||||
|
||||
CREATE INDEX idx_task_conditions_task_id ON task_conditions(task_id);
|
||||
|
||||
COMMENT ON TABLE task_conditions IS 'Reusable unlock conditions based on task completion';
|
||||
|
||||
-- Score conditions table (depends on projects, users)
|
||||
CREATE TABLE score_conditions (
|
||||
id SERIAL PRIMARY KEY,
|
||||
project_id INTEGER NOT NULL REFERENCES projects(id) ON DELETE CASCADE,
|
||||
required_points NUMERIC(10,4) NOT NULL,
|
||||
start_date DATE,
|
||||
created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
|
||||
user_id INTEGER REFERENCES users(id) ON DELETE CASCADE,
|
||||
CONSTRAINT unique_score_condition UNIQUE (project_id, required_points, start_date)
|
||||
);
|
||||
|
||||
CREATE INDEX idx_score_conditions_project_id ON score_conditions(project_id);
|
||||
CREATE INDEX idx_score_conditions_user_id ON score_conditions(user_id);
|
||||
|
||||
COMMENT ON TABLE score_conditions IS 'Reusable unlock conditions based on project points';
|
||||
COMMENT ON COLUMN score_conditions.start_date IS 'Date from which to start counting points. NULL means count all time.';
|
||||
|
||||
-- Wishlist conditions table (depends on wishlist_items, task_conditions, score_conditions, users)
|
||||
CREATE TABLE wishlist_conditions (
|
||||
id SERIAL PRIMARY KEY,
|
||||
wishlist_item_id INTEGER NOT NULL REFERENCES wishlist_items(id) ON DELETE CASCADE,
|
||||
task_condition_id INTEGER REFERENCES task_conditions(id) ON DELETE CASCADE,
|
||||
score_condition_id INTEGER REFERENCES score_conditions(id) ON DELETE CASCADE,
|
||||
display_order INTEGER DEFAULT 0,
|
||||
created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
|
||||
user_id INTEGER REFERENCES users(id) ON DELETE CASCADE,
|
||||
CONSTRAINT check_exactly_one_condition CHECK (
|
||||
(task_condition_id IS NOT NULL AND score_condition_id IS NULL) OR
|
||||
(task_condition_id IS NULL AND score_condition_id IS NOT NULL)
|
||||
)
|
||||
);
|
||||
|
||||
CREATE INDEX idx_wishlist_conditions_item_id ON wishlist_conditions(wishlist_item_id);
|
||||
CREATE INDEX idx_wishlist_conditions_item_order ON wishlist_conditions(wishlist_item_id, display_order);
|
||||
CREATE INDEX idx_wishlist_conditions_task_condition_id ON wishlist_conditions(task_condition_id);
|
||||
CREATE INDEX idx_wishlist_conditions_score_condition_id ON wishlist_conditions(score_condition_id);
|
||||
CREATE INDEX idx_wishlist_conditions_user_id ON wishlist_conditions(user_id);
|
||||
|
||||
COMMENT ON TABLE wishlist_conditions IS 'Links between wishlist items and unlock conditions. Multiple conditions per item use AND logic.';
|
||||
COMMENT ON COLUMN wishlist_conditions.display_order IS 'Order for displaying conditions in UI';
|
||||
COMMENT ON COLUMN wishlist_conditions.user_id IS 'Owner of this condition. Each user has their own goals on shared boards';
|
||||
|
||||
-- Refresh tokens table (depends on users)
|
||||
CREATE TABLE refresh_tokens (
|
||||
id SERIAL PRIMARY KEY,
|
||||
user_id INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
||||
token_hash VARCHAR(255) NOT NULL,
|
||||
expires_at TIMESTAMP WITH TIME ZONE,
|
||||
created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
CREATE INDEX idx_refresh_tokens_user_id ON refresh_tokens(user_id);
|
||||
CREATE INDEX idx_refresh_tokens_token_hash ON refresh_tokens(token_hash);
|
||||
|
||||
-- ============================================
|
||||
-- Materialized Views
|
||||
-- ============================================
|
||||
|
||||
-- Weekly report materialized view
|
||||
CREATE MATERIALIZED VIEW weekly_report_mv AS
|
||||
SELECT
|
||||
p.id AS project_id,
|
||||
agg.report_year,
|
||||
agg.report_week,
|
||||
COALESCE(agg.total_score, 0.0000) AS total_score,
|
||||
CASE
|
||||
WHEN wg.max_score IS NULL THEN COALESCE(agg.total_score, 0.0000)
|
||||
ELSE LEAST(COALESCE(agg.total_score, 0.0000), wg.max_score)
|
||||
END AS normalized_total_score
|
||||
FROM
|
||||
projects p
|
||||
LEFT JOIN
|
||||
(
|
||||
SELECT
|
||||
n.project_id,
|
||||
EXTRACT(ISOYEAR FROM e.created_date)::INTEGER AS report_year,
|
||||
EXTRACT(WEEK FROM e.created_date)::INTEGER AS report_week,
|
||||
SUM(n.score) AS total_score
|
||||
FROM
|
||||
nodes n
|
||||
JOIN
|
||||
entries e ON n.entry_id = e.id
|
||||
GROUP BY
|
||||
1, 2, 3
|
||||
) agg
|
||||
ON p.id = agg.project_id
|
||||
LEFT JOIN
|
||||
weekly_goals wg
|
||||
ON wg.project_id = p.id
|
||||
AND wg.goal_year = agg.report_year
|
||||
AND wg.goal_week = agg.report_week
|
||||
WHERE
|
||||
p.deleted = FALSE
|
||||
ORDER BY
|
||||
p.id, agg.report_year, agg.report_week
|
||||
WITH DATA;
|
||||
|
||||
CREATE INDEX idx_weekly_report_mv_project_year_week ON weekly_report_mv(project_id, report_year, report_week);
|
||||
|
||||
COMMENT ON MATERIALIZED VIEW weekly_report_mv IS 'Materialized view aggregating weekly scores by project using ISOYEAR for correct week calculations at year boundaries. Includes all projects via LEFT JOIN. Adds normalized_total_score using weekly_goals.max_score snapshot.';
|
||||
|
||||
-- ============================================
|
||||
-- Comments
|
||||
-- ============================================
|
||||
|
||||
COMMENT ON TABLE configs IS 'Test configurations (words_count, max_cards, dictionary associations). Linked to tasks via tasks.config_id.';
|
||||
COMMENT ON TABLE wishlist_boards IS 'Wishlist boards for organizing and sharing wishes';
|
||||
COMMENT ON COLUMN wishlist_boards.invite_token IS 'Token for invite link, NULL = disabled';
|
||||
COMMENT ON COLUMN wishlist_boards.invite_enabled IS 'Whether invite link is active';
|
||||
COMMENT ON TABLE wishlist_board_members IS 'Users who joined boards via invite link (not owners)';
|
||||
COMMENT ON COLUMN wishlist_items.author_id IS 'User who created this item (may differ from board owner on shared boards)';
|
||||
COMMENT ON COLUMN wishlist_items.board_id IS 'Board this item belongs to';
|
||||
|
||||
-- ============================================
|
||||
-- Additional Tables
|
||||
-- ============================================
|
||||
|
||||
-- Eateries table
|
||||
CREATE TABLE eateries (
|
||||
id SERIAL PRIMARY KEY,
|
||||
name VARCHAR(255),
|
||||
address VARCHAR(255),
|
||||
type VARCHAR(50),
|
||||
distance DOUBLE PRECISION
|
||||
);
|
||||
|
||||
-- Interesting places table
|
||||
CREATE TABLE interesting_places (
|
||||
id INTEGER PRIMARY KEY,
|
||||
name TEXT,
|
||||
city TEXT,
|
||||
description TEXT,
|
||||
added_at TIMESTAMP WITH TIME ZONE,
|
||||
is_visited BOOLEAN,
|
||||
phone_number TEXT,
|
||||
address TEXT,
|
||||
updated_at TIMESTAMP WITH TIME ZONE
|
||||
);
|
||||
|
||||
-- Music groups table
|
||||
CREATE TABLE music_groups (
|
||||
id INTEGER PRIMARY KEY,
|
||||
name TEXT,
|
||||
possible_locations TEXT
|
||||
);
|
||||
|
||||
-- N8N chat histories table
|
||||
CREATE TABLE n8n_chat_histories (
|
||||
id SERIAL PRIMARY KEY,
|
||||
session_id VARCHAR(255) NOT NULL,
|
||||
message JSONB NOT NULL
|
||||
);
|
||||
|
||||
-- Places to visit table
|
||||
CREATE TABLE places_to_visit (
|
||||
id SERIAL PRIMARY KEY,
|
||||
name TEXT NOT NULL,
|
||||
city TEXT,
|
||||
description TEXT,
|
||||
added_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
|
||||
is_visited BOOLEAN DEFAULT FALSE,
|
||||
phone_number TEXT,
|
||||
address TEXT,
|
||||
updated_at TIMESTAMP WITH TIME ZONE
|
||||
);
|
||||
|
||||
-- Restaurants table
|
||||
CREATE TABLE restaurants (
|
||||
id SERIAL PRIMARY KEY,
|
||||
name VARCHAR(255),
|
||||
address VARCHAR(255),
|
||||
contact_info VARCHAR(255)
|
||||
);
|
||||
|
||||
-- Upcoming concerts table (depends on music_groups)
|
||||
CREATE TABLE upcoming_concerts (
|
||||
id INTEGER GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
|
||||
group_id INTEGER NOT NULL REFERENCES music_groups(id),
|
||||
scheduled_at TIMESTAMP WITH TIME ZONE NOT NULL,
|
||||
venue TEXT,
|
||||
city TEXT,
|
||||
tickets_url TEXT
|
||||
);
|
||||
|
||||
CREATE UNIQUE INDEX idx_unique_concert ON upcoming_concerts(scheduled_at, city, group_id);
|
||||
15
play-life-backend/migrations_old/README.md
Normal file
15
play-life-backend/migrations_old/README.md
Normal file
@@ -0,0 +1,15 @@
|
||||
# Архив старых миграций
|
||||
|
||||
Эта директория содержит старые SQL миграции (001-029), которые были заменены baseline миграцией `000001_baseline.up.sql`.
|
||||
|
||||
## Примечание
|
||||
|
||||
Эти миграции сохранены только для справки и истории. Они **не должны применяться** в новых установках или после перехода на golang-migrate.
|
||||
|
||||
## Новые миграции
|
||||
|
||||
Все новые миграции должны создаваться в формате golang-migrate:
|
||||
- `000002_*.up.sql` - миграция вверх
|
||||
- `000002_*.down.sql` - миграция вниз (откат)
|
||||
|
||||
Используйте команду `migrate create -ext sql -dir migrations -seq <name>` для создания новых миграций.
|
||||
347
play-life-backend/test_baseline.sh
Executable file
347
play-life-backend/test_baseline.sh
Executable file
@@ -0,0 +1,347 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Скрипт для тестирования baseline миграции на чистой БД
|
||||
# Создает тестовую БД, применяет baseline, и сравнивает схему с production
|
||||
|
||||
set -e
|
||||
|
||||
# Цвета для вывода
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
BLUE='\033[0;34m'
|
||||
NC='\033[0m' # No Color
|
||||
|
||||
# Получаем переменные окружения
|
||||
DB_HOST=${DB_HOST:-localhost}
|
||||
DB_PORT=${DB_PORT:-5432}
|
||||
DB_USER=${DB_USER:-playeng}
|
||||
DB_PASSWORD=${DB_PASSWORD:-playeng}
|
||||
DB_NAME=${DB_NAME:-playeng}
|
||||
|
||||
TEST_DB_NAME="playeng_baseline_test_$$"
|
||||
MIGRATIONS_PATH="migrations"
|
||||
TMP_DIR=$(mktemp -d)
|
||||
|
||||
echo "=== Тестирование baseline миграции на чистой БД ==="
|
||||
echo ""
|
||||
|
||||
# Добавляем ~/go/bin в PATH если migrate не найден
|
||||
if ! command -v migrate &> /dev/null; then
|
||||
export PATH="$HOME/go/bin:$PATH"
|
||||
fi
|
||||
|
||||
# Проверяем наличие необходимых инструментов
|
||||
if ! command -v migrate &> /dev/null; then
|
||||
echo -e "${RED}Ошибка: migrate не найден. Установите golang-migrate:${NC}"
|
||||
echo " brew install golang-migrate"
|
||||
echo " или"
|
||||
echo " go install -tags 'postgres' github.com/golang-migrate/migrate/v4/cmd/migrate@latest"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Определяем способ выполнения PostgreSQL команд
|
||||
PG_DUMP_CMD=""
|
||||
PG_PSQL_CMD=""
|
||||
POSTGRES_CONTAINER=""
|
||||
if command -v pg_dump &> /dev/null; then
|
||||
PG_DUMP_CMD="pg_dump"
|
||||
PG_PSQL_CMD="psql"
|
||||
else
|
||||
# Пытаемся найти PostgreSQL контейнер
|
||||
if command -v docker &> /dev/null; then
|
||||
POSTGRES_CONTAINER=$(docker ps --format "{{.Names}}" 2>/dev/null | grep -iE "(postgres|db)" | head -1)
|
||||
if [ -n "$POSTGRES_CONTAINER" ]; then
|
||||
PG_DUMP_CMD="docker exec $POSTGRES_CONTAINER pg_dump"
|
||||
PG_PSQL_CMD="docker exec -i $POSTGRES_CONTAINER psql"
|
||||
echo -e "${BLUE}Используется PostgreSQL из Docker контейнера: $POSTGRES_CONTAINER${NC}"
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
||||
HAS_PG_DUMP=false
|
||||
if [ -n "$PG_DUMP_CMD" ]; then
|
||||
HAS_PG_DUMP=true
|
||||
else
|
||||
echo -e "${YELLOW}Предупреждение: pg_dump не найден. Сравнение схем будет пропущено.${NC}"
|
||||
echo " Для полного тестирования установите PostgreSQL client tools"
|
||||
fi
|
||||
|
||||
# Проверяем наличие директории миграций
|
||||
if [ ! -d "$MIGRATIONS_PATH" ]; then
|
||||
echo -e "${RED}Ошибка: Директория миграций не найдена: $MIGRATIONS_PATH${NC}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Проверяем наличие baseline миграции
|
||||
if [ ! -f "$MIGRATIONS_PATH/000001_baseline.up.sql" ]; then
|
||||
echo -e "${RED}Ошибка: Baseline миграция не найдена: $MIGRATIONS_PATH/000001_baseline.up.sql${NC}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "Параметры подключения:"
|
||||
echo " Host: $DB_HOST"
|
||||
echo " Port: $DB_PORT"
|
||||
echo " User: $DB_USER"
|
||||
echo " Test DB: $TEST_DB_NAME"
|
||||
echo ""
|
||||
|
||||
# Проверяем подключение к БД
|
||||
echo "1. Проверка подключения к БД..."
|
||||
if [ -n "$POSTGRES_CONTAINER" ]; then
|
||||
# Используем Docker
|
||||
echo "SELECT 1;" | $PG_PSQL_CMD -U $DB_USER -d postgres > /dev/null 2>&1
|
||||
elif [ -n "$PG_PSQL_CMD" ]; then
|
||||
# Используем локальный psql
|
||||
PGPASSWORD=$DB_PASSWORD $PG_PSQL_CMD \
|
||||
-h $DB_HOST \
|
||||
-p $DB_PORT \
|
||||
-U $DB_USER \
|
||||
-d postgres \
|
||||
-c "SELECT 1;" > /dev/null 2>&1
|
||||
else
|
||||
# Пытаемся через стандартный psql
|
||||
PGPASSWORD=$DB_PASSWORD psql \
|
||||
-h $DB_HOST \
|
||||
-p $DB_PORT \
|
||||
-U $DB_USER \
|
||||
-d postgres \
|
||||
-c "SELECT 1;" > /dev/null 2>&1
|
||||
fi
|
||||
|
||||
if [ $? -ne 0 ]; then
|
||||
echo -e "${RED}Ошибка: Не удалось подключиться к БД${NC}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo -e "${GREEN}✓ Подключение успешно${NC}"
|
||||
echo ""
|
||||
|
||||
# Создаем тестовую БД
|
||||
echo "2. Создание тестовой БД..."
|
||||
if [ -n "$POSTGRES_CONTAINER" ]; then
|
||||
echo "CREATE DATABASE $TEST_DB_NAME;" | $PG_PSQL_CMD -U $DB_USER -d postgres > /dev/null 2>&1
|
||||
elif [ -n "$PG_PSQL_CMD" ]; then
|
||||
PGPASSWORD=$DB_PASSWORD $PG_PSQL_CMD \
|
||||
-h $DB_HOST \
|
||||
-p $DB_PORT \
|
||||
-U $DB_USER \
|
||||
-d postgres \
|
||||
-c "CREATE DATABASE $TEST_DB_NAME;" > /dev/null 2>&1
|
||||
else
|
||||
PGPASSWORD=$DB_PASSWORD psql \
|
||||
-h $DB_HOST \
|
||||
-p $DB_PORT \
|
||||
-U $DB_USER \
|
||||
-d postgres \
|
||||
-c "CREATE DATABASE $TEST_DB_NAME;" > /dev/null 2>&1
|
||||
fi
|
||||
|
||||
if [ $? -ne 0 ]; then
|
||||
echo -e "${RED}Ошибка: Не удалось создать тестовую БД${NC}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo -e "${GREEN}✓ Тестовая БД создана: $TEST_DB_NAME${NC}"
|
||||
echo ""
|
||||
|
||||
# Ждем немного, чтобы БД точно создалась
|
||||
sleep 1
|
||||
|
||||
# Проверяем, что БД создана
|
||||
echo "3. Проверка существования тестовой БД..."
|
||||
if [ -n "$POSTGRES_CONTAINER" ]; then
|
||||
if echo "SELECT 1 FROM pg_database WHERE datname='$TEST_DB_NAME';" | $PG_PSQL_CMD -U $DB_USER -d postgres -t | grep -q 1; then
|
||||
echo -e "${GREEN}✓ БД подтверждена${NC}"
|
||||
else
|
||||
echo -e "${RED}Ошибка: БД не найдена после создания${NC}"
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
echo ""
|
||||
|
||||
# Применяем baseline миграцию
|
||||
echo "4. Применение baseline миграции..."
|
||||
cd "$(dirname "$0")" || exit 1
|
||||
|
||||
if [ -n "$POSTGRES_CONTAINER" ]; then
|
||||
# Для Docker контейнеров используем psql напрямую, так как migrate может иметь проблемы с подключением
|
||||
echo -e "${BLUE}Применение миграции через psql (Docker)...${NC}"
|
||||
if [ -f "$MIGRATIONS_PATH/000001_baseline.up.sql" ]; then
|
||||
if cat "$MIGRATIONS_PATH/000001_baseline.up.sql" | $PG_PSQL_CMD -U $DB_USER -d $TEST_DB_NAME > /dev/null 2>&1; then
|
||||
echo -e "${GREEN}✓ Миграция применена через psql${NC}"
|
||||
# Создаем таблицу schema_migrations вручную для migrate
|
||||
echo "CREATE TABLE IF NOT EXISTS schema_migrations (version bigint NOT NULL PRIMARY KEY, dirty boolean NOT NULL);" | $PG_PSQL_CMD -U $DB_USER -d $TEST_DB_NAME > /dev/null 2>&1
|
||||
echo "INSERT INTO schema_migrations (version, dirty) VALUES (1, false) ON CONFLICT (version) DO UPDATE SET dirty = false;" | $PG_PSQL_CMD -U $DB_USER -d $TEST_DB_NAME > /dev/null 2>&1
|
||||
MIGRATE_SUCCESS=false # Устанавливаем в false, чтобы использовать psql для проверки версии
|
||||
else
|
||||
echo -e "${RED}Ошибка: Не удалось применить миграцию через psql${NC}"
|
||||
exit 1
|
||||
fi
|
||||
else
|
||||
echo -e "${RED}Ошибка: Файл миграции не найден${NC}"
|
||||
exit 1
|
||||
fi
|
||||
DATABASE_URL="postgres://$DB_USER:$DB_PASSWORD@localhost:$DB_PORT/$TEST_DB_NAME?sslmode=disable"
|
||||
else
|
||||
DATABASE_URL="postgres://$DB_USER:$DB_PASSWORD@$DB_HOST:$DB_PORT/$TEST_DB_NAME?sslmode=disable"
|
||||
if ! migrate -path "$MIGRATIONS_PATH" -database "$DATABASE_URL" up; then
|
||||
echo -e "${RED}Ошибка: Не удалось применить baseline миграцию${NC}"
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
echo -e "${GREEN}✓ Baseline миграция применена${NC}"
|
||||
echo ""
|
||||
|
||||
# Проверяем версию миграции
|
||||
echo "5. Проверка версии миграции..."
|
||||
if [ -n "$POSTGRES_CONTAINER" ] && [ "${MIGRATE_SUCCESS:-false}" = "false" ]; then
|
||||
# Проверяем версию через psql
|
||||
VERSION=$(echo "SELECT version FROM schema_migrations;" | $PG_PSQL_CMD -U $DB_USER -d $TEST_DB_NAME -t 2>/dev/null | tr -d ' ' | head -1)
|
||||
if [ -n "$VERSION" ] && [ "$VERSION" != "" ]; then
|
||||
echo " Версия: $VERSION"
|
||||
if [ "$VERSION" = "1" ]; then
|
||||
echo -e "${GREEN}✓ Версия миграции корректна${NC}"
|
||||
else
|
||||
echo -e "${YELLOW}⚠ Неожиданная версия миграции: $VERSION${NC}"
|
||||
fi
|
||||
else
|
||||
echo -e "${YELLOW}⚠ Не удалось определить версию миграции${NC}"
|
||||
fi
|
||||
else
|
||||
# Используем migrate для проверки версии
|
||||
VERSION=$(migrate -path "$MIGRATIONS_PATH" -database "$DATABASE_URL" version 2>&1)
|
||||
echo " Версия: $VERSION"
|
||||
|
||||
if echo "$VERSION" | grep -qE "^1"; then
|
||||
echo -e "${GREEN}✓ Версия миграции корректна${NC}"
|
||||
else
|
||||
echo -e "${YELLOW}⚠ Неожиданная версия миграции${NC}"
|
||||
fi
|
||||
fi
|
||||
echo ""
|
||||
|
||||
# Экспортируем схему из тестовой БД (если pg_dump доступен)
|
||||
if [ "$HAS_PG_DUMP" = true ]; then
|
||||
echo "6. Экспорт схемы из тестовой БД..."
|
||||
if [ -n "$POSTGRES_CONTAINER" ]; then
|
||||
$PG_DUMP_CMD -U $DB_USER -d $TEST_DB_NAME --schema-only --no-owner --no-privileges > "$TMP_DIR/baseline_schema.sql"
|
||||
else
|
||||
PGPASSWORD=$DB_PASSWORD $PG_DUMP_CMD \
|
||||
-h $DB_HOST \
|
||||
-p $DB_PORT \
|
||||
-U $DB_USER \
|
||||
-d $TEST_DB_NAME \
|
||||
--schema-only \
|
||||
--no-owner \
|
||||
--no-privileges \
|
||||
-f "$TMP_DIR/baseline_schema.sql"
|
||||
fi
|
||||
|
||||
if [ $? -ne 0 ]; then
|
||||
echo -e "${RED}Ошибка: Не удалось экспортировать схему${NC}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo -e "${GREEN}✓ Схема экспортирована${NC}"
|
||||
echo ""
|
||||
|
||||
# Пытаемся экспортировать схему из production БД для сравнения
|
||||
echo "7. Экспорт схемы из production БД для сравнения..."
|
||||
if [ -n "$POSTGRES_CONTAINER" ]; then
|
||||
if $PG_DUMP_CMD -U $DB_USER -d $DB_NAME --schema-only --no-owner --no-privileges > "$TMP_DIR/production_schema.sql" 2>/dev/null; then
|
||||
PROD_EXPORT_SUCCESS=true
|
||||
else
|
||||
PROD_EXPORT_SUCCESS=false
|
||||
fi
|
||||
else
|
||||
if PGPASSWORD=$DB_PASSWORD $PG_DUMP_CMD \
|
||||
-h $DB_HOST \
|
||||
-p $DB_PORT \
|
||||
-U $DB_USER \
|
||||
-d $DB_NAME \
|
||||
--schema-only \
|
||||
--no-owner \
|
||||
--no-privileges \
|
||||
-f "$TMP_DIR/production_schema.sql" 2>/dev/null; then
|
||||
PROD_EXPORT_SUCCESS=true
|
||||
else
|
||||
PROD_EXPORT_SUCCESS=false
|
||||
fi
|
||||
fi
|
||||
|
||||
if [ "$PROD_EXPORT_SUCCESS" = true ]; then
|
||||
|
||||
echo -e "${GREEN}✓ Схема production экспортирована${NC}"
|
||||
echo ""
|
||||
|
||||
# Сравниваем схемы
|
||||
echo "8. Сравнение схем..."
|
||||
|
||||
# Подсчитываем объекты
|
||||
echo -e "${BLUE}Таблицы:${NC}"
|
||||
BASELINE_TABLES=$(grep -c "CREATE TABLE" "$TMP_DIR/baseline_schema.sql" || echo "0")
|
||||
PROD_TABLES=$(grep -c "CREATE TABLE" "$TMP_DIR/production_schema.sql" || echo "0")
|
||||
echo " Baseline: $BASELINE_TABLES"
|
||||
echo " Production: $PROD_TABLES"
|
||||
|
||||
if [ "$BASELINE_TABLES" -eq "$PROD_TABLES" ]; then
|
||||
echo -e " ${GREEN}✓ Количество таблиц совпадает${NC}"
|
||||
else
|
||||
echo -e " ${YELLOW}⚠ Количество таблиц не совпадает${NC}"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo -e "${BLUE}Индексы:${NC}"
|
||||
BASELINE_INDEXES=$(grep -c "CREATE.*INDEX" "$TMP_DIR/baseline_schema.sql" || echo "0")
|
||||
PROD_INDEXES=$(grep -c "CREATE.*INDEX" "$TMP_DIR/production_schema.sql" || echo "0")
|
||||
echo " Baseline: $BASELINE_INDEXES"
|
||||
echo " Production: $PROD_INDEXES"
|
||||
|
||||
if [ "$BASELINE_INDEXES" -eq "$PROD_INDEXES" ]; then
|
||||
echo -e " ${GREEN}✓ Количество индексов совпадает${NC}"
|
||||
else
|
||||
echo -e " ${YELLOW}⚠ Количество индексов не совпадает${NC}"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo -e "${BLUE}Materialized Views:${NC}"
|
||||
BASELINE_MV=$(grep -c "CREATE MATERIALIZED VIEW" "$TMP_DIR/baseline_schema.sql" || echo "0")
|
||||
PROD_MV=$(grep -c "CREATE MATERIALIZED VIEW" "$TMP_DIR/production_schema.sql" || echo "0")
|
||||
echo " Baseline: $BASELINE_MV"
|
||||
echo " Production: $PROD_MV"
|
||||
|
||||
if [ "$BASELINE_MV" -eq "$PROD_MV" ]; then
|
||||
echo -e " ${GREEN}✓ Количество materialized views совпадает${NC}"
|
||||
else
|
||||
echo -e " ${YELLOW}⚠ Количество materialized views не совпадает${NC}"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "Для детального сравнения выполните:"
|
||||
echo " diff $TMP_DIR/baseline_schema.sql $TMP_DIR/production_schema.sql"
|
||||
echo ""
|
||||
echo "Или используйте:"
|
||||
echo " diff -u $TMP_DIR/baseline_schema.sql $TMP_DIR/production_schema.sql | less"
|
||||
|
||||
else
|
||||
echo -e "${YELLOW}⚠ Не удалось экспортировать схему production БД${NC}"
|
||||
echo " Продолжаем без сравнения"
|
||||
echo ""
|
||||
echo "Схема baseline сохранена в: $TMP_DIR/baseline_schema.sql"
|
||||
fi
|
||||
else
|
||||
echo "6. Пропуск экспорта схемы (pg_dump недоступен)"
|
||||
echo ""
|
||||
echo -e "${YELLOW}Для полного тестирования установите PostgreSQL client tools:${NC}"
|
||||
echo " macOS: brew install postgresql"
|
||||
echo " или используйте Docker контейнер с PostgreSQL"
|
||||
echo ""
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "=== Тестирование завершено ==="
|
||||
echo ""
|
||||
echo -e "${GREEN}✓ Baseline миграция успешно применена к чистой БД${NC}"
|
||||
echo ""
|
||||
144
play-life-backend/validate_baseline.sh
Executable file
144
play-life-backend/validate_baseline.sh
Executable file
@@ -0,0 +1,144 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Скрипт для проверки полноты baseline миграции
|
||||
# Сравнивает текущую схему БД с baseline миграцией
|
||||
|
||||
set -e
|
||||
|
||||
# Цвета для вывода
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
NC='\033[0m' # No Color
|
||||
|
||||
# Получаем переменные окружения
|
||||
DB_HOST=${DB_HOST:-localhost}
|
||||
DB_PORT=${DB_PORT:-5432}
|
||||
DB_USER=${DB_USER:-playeng}
|
||||
DB_PASSWORD=${DB_PASSWORD:-playeng}
|
||||
DB_NAME=${DB_NAME:-playeng}
|
||||
|
||||
echo "=== Проверка полноты baseline миграции ==="
|
||||
echo ""
|
||||
|
||||
# Проверяем наличие pg_dump
|
||||
if ! command -v pg_dump &> /dev/null; then
|
||||
echo -e "${RED}Ошибка: pg_dump не найден. Установите PostgreSQL client tools.${NC}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Создаем временную директорию
|
||||
TMP_DIR=$(mktemp -d)
|
||||
trap "rm -rf $TMP_DIR" EXIT
|
||||
|
||||
echo "1. Экспортируем текущую схему БД..."
|
||||
PGPASSWORD=$DB_PASSWORD pg_dump \
|
||||
-h $DB_HOST \
|
||||
-p $DB_PORT \
|
||||
-U $DB_USER \
|
||||
-d $DB_NAME \
|
||||
--schema-only \
|
||||
--no-owner \
|
||||
--no-privileges \
|
||||
-f "$TMP_DIR/current_schema.sql"
|
||||
|
||||
if [ $? -ne 0 ]; then
|
||||
echo -e "${RED}Ошибка: Не удалось экспортировать схему БД${NC}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo -e "${GREEN}✓ Схема экспортирована${NC}"
|
||||
echo ""
|
||||
|
||||
# Применяем baseline миграцию к временной БД для сравнения
|
||||
echo "2. Создаем временную БД для проверки baseline..."
|
||||
TEMP_DB_NAME="playeng_baseline_test_$$"
|
||||
PGPASSWORD=$DB_PASSWORD psql \
|
||||
-h $DB_HOST \
|
||||
-p $DB_PORT \
|
||||
-U $DB_USER \
|
||||
-d postgres \
|
||||
-c "CREATE DATABASE $TEMP_DB_NAME;" > /dev/null 2>&1
|
||||
|
||||
if [ $? -ne 0 ]; then
|
||||
echo -e "${YELLOW}Предупреждение: Не удалось создать временную БД. Продолжаем без неё.${NC}"
|
||||
TEMP_DB_NAME=""
|
||||
else
|
||||
echo -e "${GREEN}✓ Временная БД создана${NC}"
|
||||
fi
|
||||
|
||||
# Очистка временной БД при выходе
|
||||
if [ -n "$TEMP_DB_NAME" ]; then
|
||||
trap "PGPASSWORD=$DB_PASSWORD psql -h $DB_HOST -p $DB_PORT -U $DB_USER -d postgres -c 'DROP DATABASE IF EXISTS $TEMP_DB_NAME;' > /dev/null 2>&1; rm -rf $TMP_DIR" EXIT
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "3. Анализ схемы..."
|
||||
|
||||
# Извлекаем только CREATE TABLE, CREATE INDEX, CREATE VIEW и т.д. из текущей схемы
|
||||
grep -E "^(CREATE|ALTER|COMMENT)" "$TMP_DIR/current_schema.sql" | \
|
||||
sed 's/--.*$//' | \
|
||||
tr -d '\n' | \
|
||||
sed 's/;/;\n/g' | \
|
||||
sort > "$TMP_DIR/current_clean.sql"
|
||||
|
||||
# Извлекаем из baseline миграции
|
||||
BASELINE_FILE="play-life-backend/migrations/000001_baseline.up.sql"
|
||||
if [ ! -f "$BASELINE_FILE" ]; then
|
||||
echo -e "${RED}Ошибка: Baseline файл не найден: $BASELINE_FILE${NC}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
grep -E "^(CREATE|ALTER|COMMENT)" "$BASELINE_FILE" | \
|
||||
sed 's/--.*$//' | \
|
||||
tr -d '\n' | \
|
||||
sed 's/;/;\n/g' | \
|
||||
sort > "$TMP_DIR/baseline_clean.sql"
|
||||
|
||||
echo ""
|
||||
echo "4. Сравнение..."
|
||||
|
||||
# Сравниваем количество таблиц
|
||||
CURRENT_TABLES=$(grep -c "CREATE TABLE" "$TMP_DIR/current_schema.sql" || echo "0")
|
||||
BASELINE_TABLES=$(grep -c "CREATE TABLE" "$BASELINE_FILE" || echo "0")
|
||||
|
||||
echo " Текущая БД: $CURRENT_TABLES таблиц"
|
||||
echo " Baseline: $BASELINE_TABLES таблиц"
|
||||
|
||||
if [ "$CURRENT_TABLES" -ne "$BASELINE_TABLES" ]; then
|
||||
echo -e "${YELLOW}⚠ Количество таблиц не совпадает${NC}"
|
||||
else
|
||||
echo -e "${GREEN}✓ Количество таблиц совпадает${NC}"
|
||||
fi
|
||||
|
||||
# Сравниваем количество индексов
|
||||
CURRENT_INDEXES=$(grep -c "CREATE.*INDEX" "$TMP_DIR/current_schema.sql" || echo "0")
|
||||
BASELINE_INDEXES=$(grep -c "CREATE.*INDEX" "$BASELINE_FILE" || echo "0")
|
||||
|
||||
echo " Текущая БД: $CURRENT_INDEXES индексов"
|
||||
echo " Baseline: $BASELINE_INDEXES индексов"
|
||||
|
||||
if [ "$CURRENT_INDEXES" -ne "$BASELINE_INDEXES" ]; then
|
||||
echo -e "${YELLOW}⚠ Количество индексов не совпадает${NC}"
|
||||
else
|
||||
echo -e "${GREEN}✓ Количество индексов совпадает${NC}"
|
||||
fi
|
||||
|
||||
# Проверяем наличие materialized view
|
||||
CURRENT_MV=$(grep -c "CREATE MATERIALIZED VIEW" "$TMP_DIR/current_schema.sql" || echo "0")
|
||||
BASELINE_MV=$(grep -c "CREATE MATERIALIZED VIEW" "$BASELINE_FILE" || echo "0")
|
||||
|
||||
echo " Текущая БД: $CURRENT_MV materialized views"
|
||||
echo " Baseline: $BASELINE_MV materialized views"
|
||||
|
||||
if [ "$CURRENT_MV" -ne "$BASELINE_MV" ]; then
|
||||
echo -e "${YELLOW}⚠ Количество materialized views не совпадает${NC}"
|
||||
else
|
||||
echo -e "${GREEN}✓ Количество materialized views совпадает${NC}"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "=== Проверка завершена ==="
|
||||
echo ""
|
||||
echo "Для детального сравнения выполните:"
|
||||
echo " diff $TMP_DIR/current_schema.sql $BASELINE_FILE"
|
||||
Reference in New Issue
Block a user