feat: добавлена система миграций БД и CI/CD пайплайны

- Создана система отслеживания миграций (MigrationRepository, таблица migrations)
- Добавлен скрипт apply_migrations.py для автоматического применения миграций
- Созданы CI/CD пайплайны (.github/workflows/ci.yml, deploy.yml)
- Обновлена документация по миграциям в database-patterns.md
- Миграции применяются автоматически при деплое в продакшн
This commit is contained in:
2026-01-25 23:17:09 +03:00
parent 07e72c4d14
commit e2b1353408
109 changed files with 1342 additions and 1441 deletions

View File

@@ -112,6 +112,106 @@ class UserRepository(DatabaseConnection):
## Миграции
- SQL миграции в `database/schema.sql`
- Python скрипты для миграций в `scripts/`
- Всегда проверяйте существование таблиц перед созданием: `CREATE TABLE IF NOT EXISTS`
### Обзор
Система миграций автоматически отслеживает и применяет изменения схемы БД. Миграции хранятся в `scripts/` и применяются автоматически при деплое.
### Создание миграции
1. **Создайте файл** в `scripts/` с понятным именем (например, `add_user_email_column.py`)
2. **Обязательные требования:**
- Функция `async def main(db_path: str)`
- Использует `aiosqlite` для работы с БД
- **Идемпотентна** - можно запускать несколько раз без ошибок
- Проверяет текущее состояние перед применением изменений
3. **Пример структуры:**
```python
#!/usr/bin/env python3
import argparse
import asyncio
import os
import sys
from pathlib import Path
project_root = Path(__file__).resolve().parent.parent
sys.path.insert(0, str(project_root))
import aiosqlite
from logs.custom_logger import logger
DEFAULT_DB_PATH = "database/tg-bot-database.db"
async def main(db_path: str) -> None:
"""Основная функция миграции."""
db_path = os.path.abspath(db_path)
if not os.path.exists(db_path):
logger.error(f"База данных не найдена: {db_path}")
return
async with aiosqlite.connect(db_path) as conn:
await conn.execute("PRAGMA foreign_keys = ON")
# Проверяем текущее состояние
cursor = await conn.execute("PRAGMA table_info(users)")
columns = await cursor.fetchall()
# Проверяем, нужно ли применять изменения
column_exists = any(col[1] == "email" for col in columns)
if not column_exists:
await conn.execute("ALTER TABLE users ADD COLUMN email TEXT")
await conn.commit()
logger.info("Колонка email добавлена")
else:
logger.info("Колонка email уже существует")
if __name__ == "__main__":
parser = argparse.ArgumentParser(description="Добавление колонки email")
parser.add_argument(
"--db",
default=os.environ.get("DATABASE_PATH", DEFAULT_DB_PATH),
help="Путь к БД",
)
args = parser.parse_args()
asyncio.run(main(args.db))
```
### Применение миграций
**Локально:**
```bash
python3 scripts/apply_migrations.py --dry-run # проверить
python3 scripts/apply_migrations.py # применить
```
**В продакшене:** Применяются автоматически при деплое через CI/CD (перед перезапуском контейнера).
### Важные правила
1. **Идемпотентность** - всегда проверяйте состояние перед изменением:
```python
# ✅ Правильно
cursor = await conn.execute("PRAGMA table_info(users)")
columns = await cursor.fetchall()
if not any(col[1] == "email" for col in columns):
await conn.execute("ALTER TABLE users ADD COLUMN email TEXT")
# ❌ Неправильно - упадет при повторном запуске
await conn.execute("ALTER TABLE users ADD COLUMN email TEXT")
```
2. **Порядок применения** - миграции применяются в алфавитном порядке по имени файла
3. **Исключения** - следующие скрипты не считаются миграциями:
- `apply_migrations.py`, `backfill_migrations.py`, `test_s3_connection.py`, `voice_cleanup.py`
### Регистрация существующих миграций
Если миграции уже применены, но не зарегистрированы:
```bash
python3 scripts/backfill_migrations.py # зарегистрировать все существующие
```