Files

1 line
66 KiB
JSON

{"repo": "aleksekek/news-graph-project", "files": {"src/processing/ner/factory.py": {"e699aab579d6f184a4cbdb4c48534ab624fdebe7": {"t": 1785745451.873748, "n": 1369, "h": "dd3556cef0225f45", "c": "\"\"\"\nФабрика NER-клиентов. Возвращает NatashaClient (sync) или LLMNERClient (async)\nв зависимости от settings.NER_ENGINE.\n\nUse case:\n from src.processing.ner.factory import create_ner_client\n ner = create_ner_client()\n result = ner.extract(title, text)\n if asyncio.iscoroutine(result): # LLM-клиент async\n entities = await result\n else: # Natasha sync\n entities = result\n\"\"\"\n\nfrom src.config.settings import settings\nfrom src.utils.logging import get_logger\n\nlogger = get_logger(\"ner.factory\")\n\n\ndef create_ner_client():\n \"\"\"Создаёт NER-клиент согласно settings.NER_ENGINE.\n\n Returns:\n NatashaClient (sync .extract) или LLMNERClient (async .extract).\n\n Raises:\n ValueError: при неизвестном значении NER_ENGINE.\n RuntimeError: если для LLM не задан DEEPSEEK_API_KEY.\n \"\"\"\n engine = (settings.NER_ENGINE or \"natasha\").strip().lower()\n\n if engine == \"llm\":\n from src.processing.ner.llm_client import LLMNERClient\n\n logger.info(\"NER engine: LLM (DeepSeek)\")\n return LLMNERClient()\n\n if engine == \"natasha\":\n from src.processing.ner.natasha_client import NatashaClient\n\n logger.info(\"NER engine: Natasha (local)\")\n return NatashaClient()\n\n raise ValueError(\n f\"Unknown NER_ENGINE: {engine!r}. Use 'natasha' or 'llm'.\"\n )\n"}, "0068eed12dc931bcdacf16e51ab1a07b25b48023": {"t": 1785753393.680964, "n": 1369, "h": "dd3556cef0225f45", "c": "\"\"\"\nФабрика NER-клиентов. Возвращает NatashaClient (sync) или LLMNERClient (async)\nв зависимости от settings.NER_ENGINE.\n\nUse case:\n from src.processing.ner.factory import create_ner_client\n ner = create_ner_client()\n result = ner.extract(title, text)\n if asyncio.iscoroutine(result): # LLM-клиент async\n entities = await result\n else: # Natasha sync\n entities = result\n\"\"\"\n\nfrom src.config.settings import settings\nfrom src.utils.logging import get_logger\n\nlogger = get_logger(\"ner.factory\")\n\n\ndef create_ner_client():\n \"\"\"Создаёт NER-клиент согласно settings.NER_ENGINE.\n\n Returns:\n NatashaClient (sync .extract) или LLMNERClient (async .extract).\n\n Raises:\n ValueError: при неизвестном значении NER_ENGINE.\n RuntimeError: если для LLM не задан DEEPSEEK_API_KEY.\n \"\"\"\n engine = (settings.NER_ENGINE or \"natasha\").strip().lower()\n\n if engine == \"llm\":\n from src.processing.ner.llm_client import LLMNERClient\n\n logger.info(\"NER engine: LLM (DeepSeek)\")\n return LLMNERClient()\n\n if engine == \"natasha\":\n from src.processing.ner.natasha_client import NatashaClient\n\n logger.info(\"NER engine: Natasha (local)\")\n return NatashaClient()\n\n raise ValueError(\n f\"Unknown NER_ENGINE: {engine!r}. Use 'natasha' or 'llm'.\"\n )\n"}}, ".github/workflows/deploy.yml": {"e699aab579d6f184a4cbdb4c48534ab624fdebe7": {"t": 1785747052.0085802, "n": 5470, "h": "6cfacb6c3983665a", "c": "name: Deploy to VPS\n\non:\n push:\n branches: [ \"main\" ]\n pull_request:\n branches: [ \"main\" ]\n workflow_dispatch:\n\njobs:\n # === JOB 1: Тестирование ===\n test:\n name: 🧪 Run Tests\n runs-on: ubuntu-latest\n if: \"!contains(github.event.head_commit.message, '[skip ci]')\"\n \n steps:\n - name: Checkout code\n uses: actions/checkout@v4.2.2\n \n - name: Set up Python\n uses: actions/setup-python@v5.6.0\n with:\n python-version: \"3.11\"\n \n - name: Cache pip packages\n uses: actions/cache@v4\n with:\n path: ~/.cache/pip\n key: ${{ runner.os }}-pip-${{ hashFiles('requirements.txt') }}\n restore-keys: |\n ${{ runner.os }}-pip-\n \n - name: Install dependencies\n run: |\n python -m pip install --upgrade pip\n pip install -r requirements.txt\n pip install pytest pytest-asyncio pytest-cov pytest-mock\n \n - name: Run tests\n env:\n DEEPSEEK_API_KEY: ${{ secrets.DEEPSEEK_API_KEY }}\n TELEGRAM_BOT_TOKEN: ${{ secrets.TELEGRAM_BOT_TOKEN }}\n DB_HOST: ${{ secrets.DB_HOST }}\n DB_PORT: ${{ secrets.DB_PORT }}\n DB_NAME: ${{ secrets.DB_NAME }}\n DB_USER: ${{ secrets.DB_USER }}\n DB_PASSWORD: ${{ secrets.DB_PASSWORD }}\n run: |\n pytest tests/unit/ -v --cov=src --cov-report=xml --cov-report=term\n \n - name: Upload coverage\n uses: actions/upload-artifact@v4.6.2\n if: always()\n with:\n name: coverage-report\n path: coverage.xml\n retention-days: 7\n\n # === JOB 2: Линтинг ===\n lint:\n name: 🔍 Lint Check\n runs-on: ubuntu-latest\n if: \"!contains(github.event.head_commit.message, '[skip ci]')\"\n \n steps:\n - name: Checkout code\n uses: actions/checkout@v4.2.2\n \n - name: Set up Python\n uses: actions/setup-python@v5.6.0\n with:\n python-version: \"3.11\"\n \n - name: Install linters\n run: |\n pip install ruff black isort\n \n - name: Run ruff\n run: ruff check src/ --output-format=github\n\n - name: Run black check\n run: black --check src/ || true\n\n - name: Run isort check\n run: isort --check-only --profile black src/ || true\n\n # === JOB 3: Деплой (self-hosted) ===\n deploy:\n name: 🚀 Deploy to VPS\n runs-on: self-hosted\n needs: [test, lint]\n if: github.ref == 'refs/heads/main' && success() && !contains(github.event.head_commit.message, '[skip ci]')\n \n steps:\n - name: Checkout code\n uses: actions/checkout@v4.2.2\n\n - name: Sync code to project directory\n run: | \n # Синхронизируем в реальную папку проекта\n rsync -av --delete --exclude='.git' \\\n ${{ github.workspace }}/ \\\n /home/newsapp/news_graph/\n\n - name: Create .env file\n run: |\n cd /home/newsapp/news_graph\n \n cat > .env << 'EOF'\n # База данных\n DB_HOST=${{ secrets.DB_HOST }}\n DB_PORT=${{ secrets.DB_PORT }}\n DB_NAME=${{ secrets.DB_NAME }}\n DB_USER=${{ secrets.DB_USER }}\n DB_PASSWORD=${{ secrets.DB_PASSWORD }}\n\n # LLM\n DEEPSEEK_API_KEY=${{ secrets.DEEPSEEK_API_KEY }}\n\n # NER engine: natasha | llm\n NER_ENGINE=${{ vars.NER_ENGINE }}\n NER_BATCH_SIZE=${{ vars.NER_BATCH_SIZE }}\n NER_BATCH_CONCURRENCY=${{ vars.NER_BATCH_CONCURRENCY }}\n\n # Парсеры\n PARSER_REQUEST_DELAY=${{ vars.PARSER_REQUEST_DELAY }}\n PARSER_MAX_RETRIES=${{ vars.PARSER_MAX_RETRIES }}\n PARSER_TIMEOUT=${{ vars.PARSER_TIMEOUT }}\n\n # Логирование\n LOG_LEVEL=${{ vars.LOG_LEVEL }}\n LOG_DIR=${{ vars.LOG_DIR }}\n\n # Телеграм\n TELEGRAM_BOT_TOKEN=${{ secrets.TELEGRAM_BOT_TOKEN }}\n ADMIN_CHAT_ID=${{ secrets.ADMIN_CHAT_ID }}\n PROXY_URL=${{ secrets.PROXY_URL }}\n\n # Время\n TIMEZONE=${{ vars.TIMEZONE }}\n EOF\n \n echo \"✅ .env file created at /home/newsapp/news_graph\"\n\n - name: Deploy with Docker Compose\n run: |\n cd /home/newsapp/news_graph\n \n # Останавливаем контейнеры (-v не используем — volumes не трогаем)\n docker-compose down --remove-orphans || true\n \n # Собираем и запускаем заново\n docker-compose up -d --build --remove-orphans\n \n # Очищаем старые образы\n docker image prune -f\n \n echo \"✅ Deployment completed\"\n\n - name: Check container status\n run: |\n cd /home/newsapp/news_graph\n sleep 10\n docker-compose ps\n\n # Падаем если любой контейнер не запустился\n if docker-compose ps | grep -E \"Exit|exited|error\"; then\n echo \"❌ Один или несколько контейнеров упали после старта\"\n docker-compose logs --tail=30\n exit 1\n fi\n echo \"✅ Все контейнеры работают\"\n\n - name: Notify on failure\n if: failure()\n run: |\n curl -s -X POST \"https://api.telegram.org/bot${{ secrets.TELEGRAM_BOT_TOKEN }}/sendMessage\" \\\n -d chat_id=\"${{ secrets.ADMIN_CHAT_ID }}\" \\\n -d text=\"❌ Deploy failed on commit $(echo '${{ github.sha }}' | cut -c1-7): ${{ github.event.head_commit.message }}\" \\\n || true"}, "7608cc434183c7f64e798e6dab3adfeaf3085936": {"t": 1785753396.0522287, "n": 5470, "h": "6cfacb6c3983665a", "c": "name: Deploy to VPS\n\non:\n push:\n branches: [ \"main\" ]\n pull_request:\n branches: [ \"main\" ]\n workflow_dispatch:\n\njobs:\n # === JOB 1: Тестирование ===\n test:\n name: 🧪 Run Tests\n runs-on: ubuntu-latest\n if: \"!contains(github.event.head_commit.message, '[skip ci]')\"\n \n steps:\n - name: Checkout code\n uses: actions/checkout@v4.2.2\n \n - name: Set up Python\n uses: actions/setup-python@v5.6.0\n with:\n python-version: \"3.11\"\n \n - name: Cache pip packages\n uses: actions/cache@v4\n with:\n path: ~/.cache/pip\n key: ${{ runner.os }}-pip-${{ hashFiles('requirements.txt') }}\n restore-keys: |\n ${{ runner.os }}-pip-\n \n - name: Install dependencies\n run: |\n python -m pip install --upgrade pip\n pip install -r requirements.txt\n pip install pytest pytest-asyncio pytest-cov pytest-mock\n \n - name: Run tests\n env:\n DEEPSEEK_API_KEY: ${{ secrets.DEEPSEEK_API_KEY }}\n TELEGRAM_BOT_TOKEN: ${{ secrets.TELEGRAM_BOT_TOKEN }}\n DB_HOST: ${{ secrets.DB_HOST }}\n DB_PORT: ${{ secrets.DB_PORT }}\n DB_NAME: ${{ secrets.DB_NAME }}\n DB_USER: ${{ secrets.DB_USER }}\n DB_PASSWORD: ${{ secrets.DB_PASSWORD }}\n run: |\n pytest tests/unit/ -v --cov=src --cov-report=xml --cov-report=term\n \n - name: Upload coverage\n uses: actions/upload-artifact@v4.6.2\n if: always()\n with:\n name: coverage-report\n path: coverage.xml\n retention-days: 7\n\n # === JOB 2: Линтинг ===\n lint:\n name: 🔍 Lint Check\n runs-on: ubuntu-latest\n if: \"!contains(github.event.head_commit.message, '[skip ci]')\"\n \n steps:\n - name: Checkout code\n uses: actions/checkout@v4.2.2\n \n - name: Set up Python\n uses: actions/setup-python@v5.6.0\n with:\n python-version: \"3.11\"\n \n - name: Install linters\n run: |\n pip install ruff black isort\n \n - name: Run ruff\n run: ruff check src/ --output-format=github\n\n - name: Run black check\n run: black --check src/ || true\n\n - name: Run isort check\n run: isort --check-only --profile black src/ || true\n\n # === JOB 3: Деплой (self-hosted) ===\n deploy:\n name: 🚀 Deploy to VPS\n runs-on: self-hosted\n needs: [test, lint]\n if: github.ref == 'refs/heads/main' && success() && !contains(github.event.head_commit.message, '[skip ci]')\n \n steps:\n - name: Checkout code\n uses: actions/checkout@v4.2.2\n\n - name: Sync code to project directory\n run: | \n # Синхронизируем в реальную папку проекта\n rsync -av --delete --exclude='.git' \\\n ${{ github.workspace }}/ \\\n /home/newsapp/news_graph/\n\n - name: Create .env file\n run: |\n cd /home/newsapp/news_graph\n \n cat > .env << 'EOF'\n # База данных\n DB_HOST=${{ secrets.DB_HOST }}\n DB_PORT=${{ secrets.DB_PORT }}\n DB_NAME=${{ secrets.DB_NAME }}\n DB_USER=${{ secrets.DB_USER }}\n DB_PASSWORD=${{ secrets.DB_PASSWORD }}\n\n # LLM\n DEEPSEEK_API_KEY=${{ secrets.DEEPSEEK_API_KEY }}\n\n # NER engine: natasha | llm\n NER_ENGINE=${{ vars.NER_ENGINE }}\n NER_BATCH_SIZE=${{ vars.NER_BATCH_SIZE }}\n NER_BATCH_CONCURRENCY=${{ vars.NER_BATCH_CONCURRENCY }}\n\n # Парсеры\n PARSER_REQUEST_DELAY=${{ vars.PARSER_REQUEST_DELAY }}\n PARSER_MAX_RETRIES=${{ vars.PARSER_MAX_RETRIES }}\n PARSER_TIMEOUT=${{ vars.PARSER_TIMEOUT }}\n\n # Логирование\n LOG_LEVEL=${{ vars.LOG_LEVEL }}\n LOG_DIR=${{ vars.LOG_DIR }}\n\n # Телеграм\n TELEGRAM_BOT_TOKEN=${{ secrets.TELEGRAM_BOT_TOKEN }}\n ADMIN_CHAT_ID=${{ secrets.ADMIN_CHAT_ID }}\n PROXY_URL=${{ secrets.PROXY_URL }}\n\n # Время\n TIMEZONE=${{ vars.TIMEZONE }}\n EOF\n \n echo \"✅ .env file created at /home/newsapp/news_graph\"\n\n - name: Deploy with Docker Compose\n run: |\n cd /home/newsapp/news_graph\n \n # Останавливаем контейнеры (-v не используем — volumes не трогаем)\n docker-compose down --remove-orphans || true\n \n # Собираем и запускаем заново\n docker-compose up -d --build --remove-orphans\n \n # Очищаем старые образы\n docker image prune -f\n \n echo \"✅ Deployment completed\"\n\n - name: Check container status\n run: |\n cd /home/newsapp/news_graph\n sleep 10\n docker-compose ps\n\n # Падаем если любой контейнер не запустился\n if docker-compose ps | grep -E \"Exit|exited|error\"; then\n echo \"❌ Один или несколько контейнеров упали после старта\"\n docker-compose logs --tail=30\n exit 1\n fi\n echo \"✅ Все контейнеры работают\"\n\n - name: Notify on failure\n if: failure()\n run: |\n curl -s -X POST \"https://api.telegram.org/bot${{ secrets.TELEGRAM_BOT_TOKEN }}/sendMessage\" \\\n -d chat_id=\"${{ secrets.ADMIN_CHAT_ID }}\" \\\n -d text=\"❌ Deploy failed on commit $(echo '${{ github.sha }}' | cut -c1-7): ${{ github.event.head_commit.message }}\" \\\n || true"}, "a9745c6c923aba4d3fb995e20ec32daa1db5015b": {"t": 1785753399.4654696, "n": 5269, "h": "dbb4fb20fedb86dc", "c": "name: Deploy to VPS\n\non:\n push:\n branches: [ \"main\" ]\n pull_request:\n branches: [ \"main\" ]\n workflow_dispatch:\n\njobs:\n # === JOB 1: Тестирование ===\n test:\n name: 🧪 Run Tests\n runs-on: ubuntu-latest\n if: \"!contains(github.event.head_commit.message, '[skip ci]')\"\n \n steps:\n - name: Checkout code\n uses: actions/checkout@v4.2.2\n \n - name: Set up Python\n uses: actions/setup-python@v5.6.0\n with:\n python-version: \"3.11\"\n \n - name: Cache pip packages\n uses: actions/cache@v4\n with:\n path: ~/.cache/pip\n key: ${{ runner.os }}-pip-${{ hashFiles('requirements.txt') }}\n restore-keys: |\n ${{ runner.os }}-pip-\n \n - name: Install dependencies\n run: |\n python -m pip install --upgrade pip\n pip install -r requirements.txt\n pip install pytest pytest-asyncio pytest-cov pytest-mock\n \n - name: Run tests\n env:\n DEEPSEEK_API_KEY: ${{ secrets.DEEPSEEK_API_KEY }}\n TELEGRAM_BOT_TOKEN: ${{ secrets.TELEGRAM_BOT_TOKEN }}\n DB_HOST: ${{ secrets.DB_HOST }}\n DB_PORT: ${{ secrets.DB_PORT }}\n DB_NAME: ${{ secrets.DB_NAME }}\n DB_USER: ${{ secrets.DB_USER }}\n DB_PASSWORD: ${{ secrets.DB_PASSWORD }}\n run: |\n pytest tests/unit/ -v --cov=src --cov-report=xml --cov-report=term\n \n - name: Upload coverage\n uses: actions/upload-artifact@v4.6.2\n if: always()\n with:\n name: coverage-report\n path: coverage.xml\n retention-days: 7\n\n # === JOB 2: Линтинг ===\n lint:\n name: 🔍 Lint Check\n runs-on: ubuntu-latest\n if: \"!contains(github.event.head_commit.message, '[skip ci]')\"\n \n steps:\n - name: Checkout code\n uses: actions/checkout@v4.2.2\n \n - name: Set up Python\n uses: actions/setup-python@v5.6.0\n with:\n python-version: \"3.11\"\n \n - name: Install linters\n run: |\n pip install ruff black isort\n \n - name: Run ruff\n run: ruff check src/ --output-format=github\n\n - name: Run black check\n run: black --check src/ || true\n\n - name: Run isort check\n run: isort --check-only --profile black src/ || true\n\n # === JOB 3: Деплой (self-hosted) ===\n deploy:\n name: 🚀 Deploy to VPS\n runs-on: self-hosted\n needs: [test, lint]\n if: github.ref == 'refs/heads/main' && success() && !contains(github.event.head_commit.message, '[skip ci]')\n \n steps:\n - name: Checkout code\n uses: actions/checkout@v4.2.2\n\n - name: Sync code to project directory\n run: | \n # Синхронизируем в реальную папку проекта\n rsync -av --delete --exclude='.git' \\\n ${{ github.workspace }}/ \\\n /home/newsapp/news_graph/\n\n - name: Create .env file\n run: |\n cd /home/newsapp/news_graph\n \n cat > .env << 'EOF'\n # База данных\n DB_HOST=${{ secrets.DB_HOST }}\n DB_PORT=${{ secrets.DB_PORT }}\n DB_NAME=${{ secrets.DB_NAME }}\n DB_USER=${{ secrets.DB_USER }}\n DB_PASSWORD=${{ secrets.DB_PASSWORD }}\n\n # LLM\n DEEPSEEK_API_KEY=${{ secrets.DEEPSEEK_API_KEY }}\n\n # Парсеры\n PARSER_REQUEST_DELAY=${{ vars.PARSER_REQUEST_DELAY }}\n PARSER_MAX_RETRIES=${{ vars.PARSER_MAX_RETRIES }}\n PARSER_TIMEOUT=${{ vars.PARSER_TIMEOUT }}\n\n # Логирование\n LOG_LEVEL=${{ vars.LOG_LEVEL }}\n LOG_DIR=${{ vars.LOG_DIR }}\n\n # Телеграм\n TELEGRAM_BOT_TOKEN=${{ secrets.TELEGRAM_BOT_TOKEN }}\n ADMIN_CHAT_ID=${{ secrets.ADMIN_CHAT_ID }}\n PROXY_URL=${{ secrets.PROXY_URL }}\n\n # Время\n TIMEZONE=${{ vars.TIMEZONE }}\n EOF\n \n echo \"✅ .env file created at /home/newsapp/news_graph\"\n\n - name: Deploy with Docker Compose\n run: |\n cd /home/newsapp/news_graph\n \n # Останавливаем контейнеры (-v не используем — volumes не трогаем)\n docker-compose down --remove-orphans || true\n \n # Собираем и запускаем заново\n docker-compose up -d --build --remove-orphans\n \n # Очищаем старые образы\n docker image prune -f\n \n echo \"✅ Deployment completed\"\n\n - name: Check container status\n run: |\n cd /home/newsapp/news_graph\n sleep 10\n docker-compose ps\n\n # Падаем если любой контейнер не запустился\n if docker-compose ps | grep -E \"Exit|exited|error\"; then\n echo \"❌ Один или несколько контейнеров упали после старта\"\n docker-compose logs --tail=30\n exit 1\n fi\n echo \"✅ Все контейнеры работают\"\n\n - name: Notify on failure\n if: failure()\n run: |\n curl -s -X POST \"https://api.telegram.org/bot${{ secrets.TELEGRAM_BOT_TOKEN }}/sendMessage\" \\\n -d chat_id=\"${{ secrets.ADMIN_CHAT_ID }}\" \\\n -d text=\"❌ Deploy failed on commit $(echo '${{ github.sha }}' | cut -c1-7): ${{ github.event.head_commit.message }}\" \\\n || true"}, "29dfe5c8d8afad0f8376253efe06658c5524a7c0": {"t": 1785753400.8723617, "n": 4608, "h": "c1011c17918e4427", "c": "name: Deploy to VPS\n\non:\n push:\n branches: [ \"main\" ]\n pull_request:\n branches: [ \"main\" ]\n workflow_dispatch:\n\njobs:\n # === JOB 1: Тестирование ===\n test:\n name: 🧪 Run Tests\n runs-on: ubuntu-latest\n if: \"!contains(github.event.head_commit.message, '[skip ci]')\"\n \n steps:\n - name: Checkout code\n uses: actions/checkout@v4\n \n - name: Set up Python\n uses: actions/setup-python@v5\n with:\n python-version: \"3.11\"\n \n - name: Cache pip packages\n uses: actions/cache@v3\n with:\n path: ~/.cache/pip\n key: ${{ runner.os }}-pip-${{ hashFiles('requirements.txt') }}\n restore-keys: |\n ${{ runner.os }}-pip-\n \n - name: Install dependencies\n run: |\n python -m pip install --upgrade pip\n pip install -r requirements.txt\n pip install pytest pytest-asyncio pytest-cov pytest-mock\n \n - name: Run tests\n env:\n DEEPSEEK_API_KEY: ${{ secrets.DEEPSEEK_API_KEY }}\n TELEGRAM_BOT_TOKEN: ${{ secrets.TELEGRAM_BOT_TOKEN }}\n DB_HOST: ${{ secrets.DB_HOST }}\n DB_PORT: ${{ secrets.DB_PORT }}\n DB_NAME: ${{ secrets.DB_NAME }}\n DB_USER: ${{ secrets.DB_USER }}\n DB_PASSWORD: ${{ secrets.DB_PASSWORD }}\n run: |\n pytest tests/unit/ tests/integration/test_parsers_real.py -v --cov=src --cov-report=xml --cov-report=term\n \n - name: Upload coverage\n uses: actions/upload-artifact@v4\n if: always()\n with:\n name: coverage-report\n path: coverage.xml\n retention-days: 7\n\n # === JOB 2: Линтинг ===\n lint:\n name: 🔍 Lint Check\n runs-on: ubuntu-latest\n if: \"!contains(github.event.head_commit.message, '[skip ci]')\"\n \n steps:\n - name: Checkout code\n uses: actions/checkout@v4\n \n - name: Set up Python\n uses: actions/setup-python@v5\n with:\n python-version: \"3.11\"\n \n - name: Install linters\n run: |\n pip install ruff black isort\n \n - name: Run ruff\n run: ruff check src/ --output-format=github || true\n \n - name: Run black check\n run: black --check src/ || true\n \n - name: Run isort check\n run: isort --check-only --profile black src/ || true\n\n # === JOB 3: Деплой (self-hosted) ===\n deploy:\n name: 🚀 Deploy to VPS\n runs-on: self-hosted\n needs: [test, lint]\n if: github.ref == 'refs/heads/main' && success() && !contains(github.event.head_commit.message, '[skip ci]')\n \n steps:\n - name: Checkout code\n uses: actions/checkout@v4\n\n - name: Sync code to project directory\n run: | \n # Синхронизируем в реальную папку проекта\n rsync -av --delete --exclude='.git' \\\n ${{ github.workspace }}/ \\\n /home/newsapp/news_graph/\n\n - name: Create .env file\n run: |\n cd /home/newsapp/news_graph\n \n cat > .env << 'EOF'\n # База данных\n DB_HOST=${{ secrets.DB_HOST }}\n DB_PORT=${{ secrets.DB_PORT }}\n DB_NAME=${{ secrets.DB_NAME }}\n DB_USER=${{ secrets.DB_USER }}\n DB_PASSWORD=${{ secrets.DB_PASSWORD }}\n\n # LLM\n DEEPSEEK_API_KEY=${{ secrets.DEEPSEEK_API_KEY }}\n\n # Парсеры\n PARSER_REQUEST_DELAY=${{ vars.PARSER_REQUEST_DELAY }}\n PARSER_MAX_RETRIES=${{ vars.PARSER_MAX_RETRIES }}\n PARSER_TIMEOUT=${{ vars.PARSER_TIMEOUT }}\n\n # Логирование\n LOG_LEVEL=${{ vars.LOG_LEVEL }}\n LOG_DIR=${{ vars.LOG_DIR }}\n\n # Телеграм\n TELEGRAM_BOT_TOKEN=${{ secrets.TELEGRAM_BOT_TOKEN }}\n ADMIN_CHAT_ID=${{ secrets.ADMIN_CHAT_ID }}\n PROXY_URL=${{ secrets.PROXY_URL }}\n\n # Время\n TIMEZONE=${{ vars.TIMEZONE }}\n EOF\n \n echo \"✅ .env file created at /home/newsapp/news_graph\"\n\n - name: Deploy with Docker Compose\n run: |\n cd /home/newsapp/news_graph\n \n # Останавливаем и удаляем все контейнеры, сети и volumes связанные с проектом\n docker-compose down -v --remove-orphans || true\n \n # Собираем и запускаем заново\n docker-compose up -d --build --remove-orphans\n \n # Очищаем старые образы\n docker image prune -f\n \n echo \"✅ Deployment completed\"\n\n - name: Check container status\n run: |\n cd /home/newsapp/news_graph\n docker-compose ps"}, "5637f10f6165b2887e046de455a0d41259867eba": {"t": 1785753404.0575972, "n": 4525, "h": "dc279f13eebe63bd", "c": "name: Deploy to VPS\n\non:\n push:\n branches: [ \"main\" ]\n pull_request:\n branches: [ \"main\" ]\n workflow_dispatch:\n\njobs:\n # === JOB 1: Тестирование ===\n test:\n name: 🧪 Run Tests\n runs-on: ubuntu-latest\n if: \"!contains(github.event.head_commit.message, '[skip ci]')\"\n \n steps:\n - name: Checkout code\n uses: actions/checkout@v4\n \n - name: Set up Python\n uses: actions/setup-python@v5\n with:\n python-version: \"3.11\"\n \n - name: Cache pip packages\n uses: actions/cache@v3\n with:\n path: ~/.cache/pip\n key: ${{ runner.os }}-pip-${{ hashFiles('requirements.txt') }}\n restore-keys: |\n ${{ runner.os }}-pip-\n \n - name: Install dependencies\n run: |\n python -m pip install --upgrade pip\n pip install -r requirements.txt\n pip install pytest pytest-asyncio pytest-cov pytest-mock\n \n - name: Run tests\n env:\n DEEPSEEK_API_KEY: ${{ secrets.DEEPSEEK_API_KEY }}\n TELEGRAM_BOT_TOKEN: ${{ secrets.TELEGRAM_BOT_TOKEN }}\n DB_HOST: ${{ secrets.DB_HOST }}\n DB_PORT: ${{ secrets.DB_PORT }}\n DB_NAME: ${{ secrets.DB_NAME }}\n DB_USER: ${{ secrets.DB_USER }}\n DB_PASSWORD: ${{ secrets.DB_PASSWORD }}\n run: |\n pytest tests --cov=src\n \n - name: Upload coverage\n uses: actions/upload-artifact@v4\n if: always()\n with:\n name: coverage-report\n path: coverage.xml\n retention-days: 7\n\n # === JOB 2: Линтинг ===\n lint:\n name: 🔍 Lint Check\n runs-on: ubuntu-latest\n if: \"!contains(github.event.head_commit.message, '[skip ci]')\"\n \n steps:\n - name: Checkout code\n uses: actions/checkout@v4\n \n - name: Set up Python\n uses: actions/setup-python@v5\n with:\n python-version: \"3.11\"\n \n - name: Install linters\n run: |\n pip install ruff black isort\n \n - name: Run ruff\n run: ruff check src/ --output-format=github || true\n \n - name: Run black check\n run: black --check src/ || true\n \n - name: Run isort check\n run: isort --check-only --profile black src/ || true\n\n # === JOB 3: Деплой (self-hosted) ===\n deploy:\n name: 🚀 Deploy to VPS\n runs-on: self-hosted\n needs: [test, lint]\n if: github.ref == 'refs/heads/main' && success() && !contains(github.event.head_commit.message, '[skip ci]')\n \n steps:\n - name: Checkout code\n uses: actions/checkout@v4\n\n - name: Sync code to project directory\n run: | \n # Синхронизируем в реальную папку проекта\n rsync -av --delete --exclude='.git' \\\n ${{ github.workspace }}/ \\\n /home/newsapp/news_graph/\n\n - name: Create .env file\n run: |\n cd /home/newsapp/news_graph\n \n cat > .env << 'EOF'\n # База данных\n DB_HOST=${{ secrets.DB_HOST }}\n DB_PORT=${{ secrets.DB_PORT }}\n DB_NAME=${{ secrets.DB_NAME }}\n DB_USER=${{ secrets.DB_USER }}\n DB_PASSWORD=${{ secrets.DB_PASSWORD }}\n\n # LLM\n DEEPSEEK_API_KEY=${{ secrets.DEEPSEEK_API_KEY }}\n\n # Парсеры\n PARSER_REQUEST_DELAY=${{ vars.PARSER_REQUEST_DELAY }}\n PARSER_MAX_RETRIES=${{ vars.PARSER_MAX_RETRIES }}\n PARSER_TIMEOUT=${{ vars.PARSER_TIMEOUT }}\n\n # Логирование\n LOG_LEVEL=${{ vars.LOG_LEVEL }}\n LOG_DIR=${{ vars.LOG_DIR }}\n\n # Телеграм\n TELEGRAM_BOT_TOKEN=${{ secrets.TELEGRAM_BOT_TOKEN }}\n ADMIN_CHAT_ID=${{ secrets.ADMIN_CHAT_ID }}\n PROXY_URL=${{ secrets.PROXY_URL }}\n\n # Время\n TIMEZONE=${{ vars.TIMEZONE }}\n EOF\n \n echo \"✅ .env file created at /home/newsapp/news_graph\"\n\n - name: Deploy with Docker Compose\n run: |\n cd /home/newsapp/news_graph\n \n # Останавливаем и удаляем все контейнеры, сети и volumes связанные с проектом\n docker-compose down -v --remove-orphans || true\n \n # Собираем и запускаем заново\n docker-compose up -d --build --remove-orphans\n \n # Очищаем старые образы\n docker image prune -f\n \n echo \"✅ Deployment completed\"\n\n - name: Check container status\n run: |\n cd /home/newsapp/news_graph\n docker-compose ps"}, "36626b1885d8d0ca975bdd062c725384a61c38ae": {"t": 1785753406.0971901, "n": 4528, "h": "b5af7e5b22ecfc95", "c": "name: Deploy to VPS\n\non:\n push:\n branches: [ \"main\" ]\n pull_request:\n branches: [ \"main\" ]\n workflow_dispatch:\n\njobs:\n # === JOB 1: Тестирование ===\n test:\n name: 🧪 Run Tests\n runs-on: ubuntu-latest\n if: \"!contains(github.event.head_commit.message, '[skip ci]')\"\n \n steps:\n - name: Checkout code\n uses: actions/checkout@v4\n \n - name: Set up Python\n uses: actions/setup-python@v5\n with:\n python-version: \"3.11\"\n \n - name: Cache pip packages\n uses: actions/cache@v3\n with:\n path: ~/.cache/pip\n key: ${{ runner.os }}-pip-${{ hashFiles('requirements.txt') }}\n restore-keys: |\n ${{ runner.os }}-pip-\n \n - name: Install dependencies\n run: |\n python -m pip install --upgrade pip\n pip install -r requirements.txt\n pip install pytest pytest-asyncio pytest-cov pytest-mock\n \n - name: Run tests\n env:\n DEEPSEEK_API_KEY: ${{ secrets.DEEPSEEK_API_KEY }}\n TELEGRAM_BOT_TOKEN: ${{ secrets.TELEGRAM_BOT_TOKEN }}\n DB_HOST: ${{ secrets.DB_HOST }}\n DB_PORT: ${{ secrets.DB_PORT }}\n DB_NAME: ${{ secrets.DB_NAME }}\n DB_USER: ${{ secrets.DB_USER }}\n DB_PASSWORD: ${{ secrets.DB_PASSWORD }}\n run: |\n pytest .\\tests\\ --cov=src\n \n - name: Upload coverage\n uses: actions/upload-artifact@v4\n if: always()\n with:\n name: coverage-report\n path: coverage.xml\n retention-days: 7\n\n # === JOB 2: Линтинг ===\n lint:\n name: 🔍 Lint Check\n runs-on: ubuntu-latest\n if: \"!contains(github.event.head_commit.message, '[skip ci]')\"\n \n steps:\n - name: Checkout code\n uses: actions/checkout@v4\n \n - name: Set up Python\n uses: actions/setup-python@v5\n with:\n python-version: \"3.11\"\n \n - name: Install linters\n run: |\n pip install ruff black isort\n \n - name: Run ruff\n run: ruff check src/ --output-format=github || true\n \n - name: Run black check\n run: black --check src/ || true\n \n - name: Run isort check\n run: isort --check-only --profile black src/ || true\n\n # === JOB 3: Деплой (self-hosted) ===\n deploy:\n name: 🚀 Deploy to VPS\n runs-on: self-hosted\n needs: [test, lint]\n if: github.ref == 'refs/heads/main' && success() && !contains(github.event.head_commit.message, '[skip ci]')\n \n steps:\n - name: Checkout code\n uses: actions/checkout@v4\n\n - name: Sync code to project directory\n run: | \n # Синхронизируем в реальную папку проекта\n rsync -av --delete --exclude='.git' \\\n ${{ github.workspace }}/ \\\n /home/newsapp/news_graph/\n\n - name: Create .env file\n run: |\n cd /home/newsapp/news_graph\n \n cat > .env << 'EOF'\n # База данных\n DB_HOST=${{ secrets.DB_HOST }}\n DB_PORT=${{ secrets.DB_PORT }}\n DB_NAME=${{ secrets.DB_NAME }}\n DB_USER=${{ secrets.DB_USER }}\n DB_PASSWORD=${{ secrets.DB_PASSWORD }}\n\n # LLM\n DEEPSEEK_API_KEY=${{ secrets.DEEPSEEK_API_KEY }}\n\n # Парсеры\n PARSER_REQUEST_DELAY=${{ vars.PARSER_REQUEST_DELAY }}\n PARSER_MAX_RETRIES=${{ vars.PARSER_MAX_RETRIES }}\n PARSER_TIMEOUT=${{ vars.PARSER_TIMEOUT }}\n\n # Логирование\n LOG_LEVEL=${{ vars.LOG_LEVEL }}\n LOG_DIR=${{ vars.LOG_DIR }}\n\n # Телеграм\n TELEGRAM_BOT_TOKEN=${{ secrets.TELEGRAM_BOT_TOKEN }}\n ADMIN_CHAT_ID=${{ secrets.ADMIN_CHAT_ID }}\n PROXY_URL=${{ secrets.PROXY_URL }}\n\n # Время\n TIMEZONE=${{ vars.TIMEZONE }}\n EOF\n \n echo \"✅ .env file created at /home/newsapp/news_graph\"\n\n - name: Deploy with Docker Compose\n run: |\n cd /home/newsapp/news_graph\n \n # Останавливаем и удаляем все контейнеры, сети и volumes связанные с проектом\n docker-compose down -v --remove-orphans || true\n \n # Собираем и запускаем заново\n docker-compose up -d --build --remove-orphans\n \n # Очищаем старые образы\n docker image prune -f\n \n echo \"✅ Deployment completed\"\n\n - name: Check container status\n run: |\n cd /home/newsapp/news_graph\n docker-compose ps"}, "e88878e260d2c33304dca3392e622b2b046ce6b5": {"t": 1785753407.2558177, "n": 4608, "h": "c1011c17918e4427", "c": "name: Deploy to VPS\n\non:\n push:\n branches: [ \"main\" ]\n pull_request:\n branches: [ \"main\" ]\n workflow_dispatch:\n\njobs:\n # === JOB 1: Тестирование ===\n test:\n name: 🧪 Run Tests\n runs-on: ubuntu-latest\n if: \"!contains(github.event.head_commit.message, '[skip ci]')\"\n \n steps:\n - name: Checkout code\n uses: actions/checkout@v4\n \n - name: Set up Python\n uses: actions/setup-python@v5\n with:\n python-version: \"3.11\"\n \n - name: Cache pip packages\n uses: actions/cache@v3\n with:\n path: ~/.cache/pip\n key: ${{ runner.os }}-pip-${{ hashFiles('requirements.txt') }}\n restore-keys: |\n ${{ runner.os }}-pip-\n \n - name: Install dependencies\n run: |\n python -m pip install --upgrade pip\n pip install -r requirements.txt\n pip install pytest pytest-asyncio pytest-cov pytest-mock\n \n - name: Run tests\n env:\n DEEPSEEK_API_KEY: ${{ secrets.DEEPSEEK_API_KEY }}\n TELEGRAM_BOT_TOKEN: ${{ secrets.TELEGRAM_BOT_TOKEN }}\n DB_HOST: ${{ secrets.DB_HOST }}\n DB_PORT: ${{ secrets.DB_PORT }}\n DB_NAME: ${{ secrets.DB_NAME }}\n DB_USER: ${{ secrets.DB_USER }}\n DB_PASSWORD: ${{ secrets.DB_PASSWORD }}\n run: |\n pytest tests/unit/ tests/integration/test_parsers_real.py -v --cov=src --cov-report=xml --cov-report=term\n \n - name: Upload coverage\n uses: actions/upload-artifact@v4\n if: always()\n with:\n name: coverage-report\n path: coverage.xml\n retention-days: 7\n\n # === JOB 2: Линтинг ===\n lint:\n name: 🔍 Lint Check\n runs-on: ubuntu-latest\n if: \"!contains(github.event.head_commit.message, '[skip ci]')\"\n \n steps:\n - name: Checkout code\n uses: actions/checkout@v4\n \n - name: Set up Python\n uses: actions/setup-python@v5\n with:\n python-version: \"3.11\"\n \n - name: Install linters\n run: |\n pip install ruff black isort\n \n - name: Run ruff\n run: ruff check src/ --output-format=github || true\n \n - name: Run black check\n run: black --check src/ || true\n \n - name: Run isort check\n run: isort --check-only --profile black src/ || true\n\n # === JOB 3: Деплой (self-hosted) ===\n deploy:\n name: 🚀 Deploy to VPS\n runs-on: self-hosted\n needs: [test, lint]\n if: github.ref == 'refs/heads/main' && success() && !contains(github.event.head_commit.message, '[skip ci]')\n \n steps:\n - name: Checkout code\n uses: actions/checkout@v4\n\n - name: Sync code to project directory\n run: | \n # Синхронизируем в реальную папку проекта\n rsync -av --delete --exclude='.git' \\\n ${{ github.workspace }}/ \\\n /home/newsapp/news_graph/\n\n - name: Create .env file\n run: |\n cd /home/newsapp/news_graph\n \n cat > .env << 'EOF'\n # База данных\n DB_HOST=${{ secrets.DB_HOST }}\n DB_PORT=${{ secrets.DB_PORT }}\n DB_NAME=${{ secrets.DB_NAME }}\n DB_USER=${{ secrets.DB_USER }}\n DB_PASSWORD=${{ secrets.DB_PASSWORD }}\n\n # LLM\n DEEPSEEK_API_KEY=${{ secrets.DEEPSEEK_API_KEY }}\n\n # Парсеры\n PARSER_REQUEST_DELAY=${{ vars.PARSER_REQUEST_DELAY }}\n PARSER_MAX_RETRIES=${{ vars.PARSER_MAX_RETRIES }}\n PARSER_TIMEOUT=${{ vars.PARSER_TIMEOUT }}\n\n # Логирование\n LOG_LEVEL=${{ vars.LOG_LEVEL }}\n LOG_DIR=${{ vars.LOG_DIR }}\n\n # Телеграм\n TELEGRAM_BOT_TOKEN=${{ secrets.TELEGRAM_BOT_TOKEN }}\n ADMIN_CHAT_ID=${{ secrets.ADMIN_CHAT_ID }}\n PROXY_URL=${{ secrets.PROXY_URL }}\n\n # Время\n TIMEZONE=${{ vars.TIMEZONE }}\n EOF\n \n echo \"✅ .env file created at /home/newsapp/news_graph\"\n\n - name: Deploy with Docker Compose\n run: |\n cd /home/newsapp/news_graph\n \n # Останавливаем и удаляем все контейнеры, сети и volumes связанные с проектом\n docker-compose down -v --remove-orphans || true\n \n # Собираем и запускаем заново\n docker-compose up -d --build --remove-orphans\n \n # Очищаем старые образы\n docker image prune -f\n \n echo \"✅ Deployment completed\"\n\n - name: Check container status\n run: |\n cd /home/newsapp/news_graph\n docker-compose ps"}, "4cdd7b16600f8177e1aad48adc09197c9a808182": {"t": 1785753408.4844677, "n": 4379, "h": "5609eb99b39b156d", "c": "name: Deploy to VPS\n\non:\n push:\n branches: [ \"main\" ]\n pull_request:\n branches: [ \"main\" ]\n workflow_dispatch:\n\njobs:\n # === JOB 1: Тестирование ===\n test:\n name: 🧪 Run Tests\n runs-on: ubuntu-latest\n if: \"!contains(github.event.head_commit.message, '[skip ci]')\"\n \n steps:\n - name: Checkout code\n uses: actions/checkout@v4\n \n - name: Set up Python\n uses: actions/setup-python@v5\n with:\n python-version: \"3.11\"\n \n - name: Cache pip packages\n uses: actions/cache@v3\n with:\n path: ~/.cache/pip\n key: ${{ runner.os }}-pip-${{ hashFiles('requirements.txt') }}\n restore-keys: |\n ${{ runner.os }}-pip-\n \n - name: Install dependencies\n run: |\n python -m pip install --upgrade pip\n pip install -r requirements.txt\n pip install pytest pytest-asyncio pytest-cov pytest-mock\n \n - name: Run tests\n env:\n DEEPSEEK_API_KEY: ${{ secrets.DEEPSEEK_API_KEY }}\n TELEGRAM_BOT_TOKEN: ${{ secrets.TELEGRAM_BOT_TOKEN }}\n DB_HOST: ${{ secrets.DB_HOST }}\n DB_PORT: ${{ secrets.DB_PORT }}\n DB_NAME: ${{ secrets.DB_NAME }}\n DB_USER: ${{ secrets.DB_USER }}\n DB_PASSWORD: ${{ secrets.DB_PASSWORD }}\n run: |\n pytest tests/unit/ tests/integration/test_parsers_real.py -v --cov=src --cov-report=xml --cov-report=term\n \n - name: Upload coverage\n uses: actions/upload-artifact@v4\n if: always()\n with:\n name: coverage-report\n path: coverage.xml\n retention-days: 7\n\n # === JOB 2: Линтинг ===\n lint:\n name: 🔍 Lint Check\n runs-on: ubuntu-latest\n if: \"!contains(github.event.head_commit.message, '[skip ci]')\"\n \n steps:\n - name: Checkout code\n uses: actions/checkout@v4\n \n - name: Set up Python\n uses: actions/setup-python@v5\n with:\n python-version: \"3.11\"\n \n - name: Install linters\n run: |\n pip install ruff black isort\n \n - name: Run ruff\n run: ruff check src/ --output-format=github || true\n \n - name: Run black check\n run: black --check src/ || true\n \n - name: Run isort check\n run: isort --check-only --profile black src/ || true\n\n # === JOB 3: Деплой (self-hosted) ===\n deploy:\n name: 🚀 Deploy to VPS\n runs-on: self-hosted\n needs: [test, lint]\n if: github.ref == 'refs/heads/main' && success() && !contains(github.event.head_commit.message, '[skip ci]')\n \n steps:\n - name: Checkout code\n uses: actions/checkout@v4\n\n - name: Sync code to project directory\n run: | \n # Синхронизируем в реальную папку проекта\n rsync -av --delete --exclude='.git' \\\n ${{ github.workspace }}/ \\\n /home/newsapp/news_graph/\n\n - name: Create .env file\n run: |\n cd /home/newsapp/news_graph\n \n cat > .env << 'EOF'\n # База данных\n DB_HOST=${{ secrets.DB_HOST }}\n DB_PORT=${{ secrets.DB_PORT }}\n DB_NAME=${{ secrets.DB_NAME }}\n DB_USER=${{ secrets.DB_USER }}\n DB_PASSWORD=${{ secrets.DB_PASSWORD }}\n\n # LLM\n DEEPSEEK_API_KEY=${{ secrets.DEEPSEEK_API_KEY }}\n\n # Парсеры\n PARSER_REQUEST_DELAY=${{ vars.PARSER_REQUEST_DELAY }}\n PARSER_MAX_RETRIES=${{ vars.PARSER_MAX_RETRIES }}\n PARSER_TIMEOUT=${{ vars.PARSER_TIMEOUT }}\n\n # Логирование\n LOG_LEVEL=${{ vars.LOG_LEVEL }}\n LOG_DIR=${{ vars.LOG_DIR }}\n\n # Телеграм\n TELEGRAM_BOT_TOKEN=${{ secrets.TELEGRAM_BOT_TOKEN }}\n ADMIN_CHAT_ID=${{ secrets.ADMIN_CHAT_ID }}\n PROXY_URL=${{ secrets.PROXY_URL }}\n\n # Время\n TIMEZONE=${{ vars.TIMEZONE }}\n EOF\n \n echo \"✅ .env file created at /home/newsapp/news_graph\"\n\n - name: Deploy with Docker Compose\n run: |\n cd /home/newsapp/news_graph\n \n docker-compose down\n docker-compose up -d --build\n docker image prune -f\n \n echo \"✅ Deployment completed\"\n\n - name: Check container status\n run: |\n cd /home/newsapp/news_graph\n docker-compose ps"}, "2702382011e906c84da5335d25cd890ce525096b": {"t": 1785753411.751977, "n": 6975, "h": "b61c2c261eeabd5a", "c": "name: Deploy to VPS\n\non:\n push:\n branches: [ \"main\" ]\n pull_request:\n branches: [ \"main\" ]\n workflow_dispatch:\n\njobs:\n # === JOB 1: Тестирование ===\n test:\n name: 🧪 Run Tests\n runs-on: ubuntu-latest\n if: \"!contains(github.event.head_commit.message, '[skip ci]')\"\n \n steps:\n - name: Checkout code\n uses: actions/checkout@v4\n \n - name: Set up Python\n uses: actions/setup-python@v5\n with:\n python-version: \"3.11\"\n \n - name: Cache pip packages\n uses: actions/cache@v3\n with:\n path: ~/.cache/pip\n key: ${{ runner.os }}-pip-${{ hashFiles('requirements.txt') }}\n restore-keys: |\n ${{ runner.os }}-pip-\n \n - name: Install dependencies\n run: |\n python -m pip install --upgrade pip\n pip install -r requirements.txt\n pip install pytest pytest-asyncio pytest-cov pytest-mock\n \n - name: Run tests\n env:\n DEEPSEEK_API_KEY: ${{ secrets.DEEPSEEK_API_KEY }}\n TELEGRAM_BOT_TOKEN: ${{ secrets.TELEGRAM_BOT_TOKEN }}\n DB_HOST: ${{ secrets.DB_HOST }}\n DB_PORT: ${{ secrets.DB_PORT }}\n DB_NAME: ${{ secrets.DB_NAME }}\n DB_USER: ${{ secrets.DB_USER }}\n DB_PASSWORD: ${{ secrets.DB_PASSWORD }}\n run: |\n pytest tests/unit/ tests/integration/test_parsers_real.py -v --cov=src --cov-report=xml --cov-report=term\n \n - name: Upload coverage\n uses: actions/upload-artifact@v4\n if: always()\n with:\n name: coverage-report\n path: coverage.xml\n retention-days: 7\n\n # === JOB 2: Линтинг ===\n lint:\n name: 🔍 Lint Check\n runs-on: ubuntu-latest\n if: \"!contains(github.event.head_commit.message, '[skip ci]')\"\n \n steps:\n - name: Checkout code\n uses: actions/checkout@v4\n \n - name: Set up Python\n uses: actions/setup-python@v5\n with:\n python-version: \"3.11\"\n \n - name: Install linters\n run: |\n pip install ruff black isort\n \n - name: Run ruff\n run: ruff check src/ --output-format=github || true\n \n - name: Run black check\n run: black --check src/ || true\n \n - name: Run isort check\n run: isort --check-only --profile black src/ || true\n\n # === JOB 3: Сборка и деплой (только для main) ===\n deploy:\n name: 🚀 Deploy to VPS\n runs-on: ubuntu-latest\n needs: [test, lint]\n if: github.ref == 'refs/heads/main' && success() && !contains(github.event.head_commit.message, '[skip ci]')\n \n steps:\n - name: Checkout code\n uses: actions/checkout@v4\n \n - name: Create .env file for deployment\n run: |\n cat > .env << 'EOF'\n # База данных\n DB_HOST=${{ secrets.DB_HOST }}\n DB_PORT=${{ secrets.DB_PORT }}\n DB_NAME=${{ secrets.DB_NAME }}\n DB_USER=${{ secrets.DB_USER }}\n DB_PASSWORD=${{ secrets.DB_PASSWORD }}\n \n # LLM\n DEEPSEEK_API_KEY=${{ secrets.DEEPSEEK_API_KEY }}\n \n # Парсеры\n PARSER_REQUEST_DELAY=${{ vars.PARSER_REQUEST_DELAY }}\n PARSER_MAX_RETRIES=${{ vars.PARSER_MAX_RETRIES }}\n PARSER_TIMEOUT=${{ vars.PARSER_TIMEOUT }}\n \n # Логирование\n LOG_LEVEL=${{ vars.LOG_LEVEL }}\n LOG_DIR=${{ vars.LOG_DIR }}\n \n # Telegram\n TELEGRAM_BOT_TOKEN=${{ secrets.TELEGRAM_BOT_TOKEN }}\n ADMIN_CHAT_ID=${{ secrets.ADMIN_CHAT_ID }}\n PROXY_URL=${{ secrets.PROXY_URL }}\n \n # Время\n TIMEZONE=${{ vars.TIMEZONE }}\n EOF\n \n echo \"✅ .env file created\"\n \n - name: Deploy via SSH\n uses: appleboy/ssh-action@v1.0.3\n with:\n host: ${{ secrets.DEPLOY_HOST }}\n username: ${{ secrets.DEPLOY_USER }}\n key: ${{ secrets.SSH_PRIVATE_KEY }}\n port: 22\n script: |\n # Переходим в директорию проекта\n cd /home/newsapp/news_graph || exit 1\n \n # Синхронизируем код (исключая .git)\n rsync -av --delete --exclude='.git' \\\n ${{ github.workspace }}/ \\\n /home/newsapp/news_graph/\n \n # Останавливаем старые контейнеры\n docker-compose down || true\n \n # Собираем и запускаем новые\n docker-compose up -d --build\n \n # Очищаем старые образы\n docker image prune -f\n \n # Проверяем статус\n docker-compose ps\n \n # Отправляем уведомление в Telegram (опционально)\n curl -s -X POST \"https://api.telegram.org/bot${{ secrets.TELEGRAM_BOT_TOKEN }}/sendMessage\" \\\n -d chat_id=${{ secrets.ADMIN_CHAT_ID }} \\\n -d text=\"✅ News Graph Project deployed successfully%0ACommit: ${{ github.sha }}%0ABranch: ${{ github.ref_name }}\"\n \n - name: Check container status\n if: always()\n run: |\n echo \"📊 Deployment status:\"\n ssh -o StrictHostKeyChecking=no ${{ secrets.DEPLOY_USER }}@${{ secrets.DEPLOY_HOST }} \\\n \"cd /home/newsapp/news_graph && docker-compose ps\"\n\n # === JOB 4: Health check ===\n health-check:\n name: 🏥 Health Check\n runs-on: ubuntu-latest\n needs: [deploy]\n if: success() && needs.deploy.result == 'success'\n \n steps:\n - name: Wait for services\n run: sleep 15\n \n - name: Check containers health\n run: |\n ssh -o StrictHostKeyChecking=no ${{ secrets.DEPLOY_USER }}@${{ secrets.DEPLOY_HOST }} << 'EOF'\n cd /home/newsapp/news_graph\n \n # Проверяем парсер\n if docker ps --filter \"name=news_graph_parser\" --format \"{{.Status}}\" | grep -q \"Up\"; then\n echo \"✅ Parser is running\"\n else\n echo \"❌ Parser is not running\"\n exit 1\n fi\n \n # Проверяем суммаризатор\n if docker ps --filter \"name=news_graph_summarizer\" --format \"{{.Status}}\" | grep -q \"Up\"; then\n echo \"✅ Summarizer is running\"\n else\n echo \"❌ Summarizer is not running\"\n exit 1\n fi\n \n # Проверяем бота\n if docker ps --filter \"name=news_graph_bot\" --format \"{{.Status}}\" | grep -q \"Up\"; then\n echo \"✅ Bot is running\"\n else\n echo \"❌ Bot is not running\"\n exit 1\n fi\n EOF\n \n - name: Send health check report\n if: always()\n run: |\n curl -s -X POST \"https://api.telegram.org/bot${{ secrets.TELEGRAM_BOT_TOKEN }}/sendMessage\" \\\n -d chat_id=${{ secrets.ADMIN_CHAT_ID }} \\\n -d text=\"🏥 Health Check Report%0A%0AAll containers are running normally\" || true"}, "b71ac55a2a12cc37816f4969ac45ad663a1771e3": {"t": 1785753413.2815402, "n": 1957, "h": "be11898a4fb305ed", "c": "name: Deploy to VPS\n\non:\n push:\n branches: [ \"main\" ]\n workflow_dispatch:\n\njobs:\n deploy:\n if: \"!contains(github.event.head_commit.message, '[skip ci]')\"\n runs-on: self-hosted\n steps:\n - name: Checkout code\n uses: actions/checkout@v4\n\n - name: Sync code to project directory\n run: | \n # Синхронизируем в реальную папку проекта\n rsync -av --delete --exclude='.git' \\\n ${{ github.workspace }}/ \\\n /home/newsapp/news_graph/\n\n - name: Create .env file\n run: |\n cd /home/newsapp/news_graph\n \n cat > .env << 'EOF'\n # База данных\n DB_HOST=${{ secrets.DB_HOST }}\n DB_PORT=${{ secrets.DB_PORT }}\n DB_NAME=${{ secrets.DB_NAME }}\n DB_USER=${{ secrets.DB_USER }}\n DB_PASSWORD=${{ secrets.DB_PASSWORD }}\n\n # LLM\n DEEPSEEK_API_KEY=${{ secrets.DEEPSEEK_API_KEY }}\n\n # Парсеры\n PARSER_REQUEST_DELAY=${{ vars.PARSER_REQUEST_DELAY }}\n PARSER_MAX_RETRIES=${{ vars.PARSER_MAX_RETRIES }}\n PARSER_TIMEOUT=${{ vars.PARSER_TIMEOUT }}\n\n # Логирование\n LOG_LEVEL=${{ vars.LOG_LEVEL }}\n LOG_DIR=${{ vars.LOG_DIR }}\n\n # Телеграм\n TELEGRAM_BOT_TOKEN=${{ secrets.TELEGRAM_BOT_TOKEN }}\n ADMIN_CHAT_ID=${{ secrets.ADMIN_CHAT_ID }}\n PROXY_URL=${{ secrets.PROXY_URL }}\n\n # Время\n TIMEZONE=${{ vars.TIMEZONE }}\n EOF\n \n echo \"✅ .env file created at /home/newsapp/news_graph\"\n\n - name: Deploy with Docker Compose\n run: |\n cd /home/newsapp/news_graph\n \n docker-compose down\n docker-compose up -d --build\n docker image prune -f\n \n echo \"✅ Deployment completed\"\n\n - name: Check container status\n run: |\n cd /home/newsapp/news_graph\n docker-compose ps"}, "dc306dbe3db3076bcd60c15d552d6429fa354a0a": {"t": 1785753414.499969, "n": 1881, "h": "50134e8d1055bffa", "c": "name: Deploy to VPS\n\non:\n push:\n branches: [ \"main\" ]\n workflow_dispatch:\n\njobs:\n deploy:\n if: \"!contains(github.event.head_commit.message, '[skip ci]')\"\n runs-on: self-hosted\n steps:\n - name: Checkout code\n uses: actions/checkout@v4\n\n - name: Sync code to project directory\n run: | \n # Синхронизируем в реальную папку проекта\n rsync -av --delete --exclude='.git' \\\n ${{ github.workspace }}/ \\\n /home/newsapp/news_graph/\n\n - name: Create .env file\n run: |\n cd /home/newsapp/news_graph\n \n cat > .env << 'EOF'\n # База данных\n DB_HOST=${{ secrets.DB_HOST }}\n DB_PORT=${{ secrets.DB_PORT }}\n DB_NAME=${{ secrets.DB_NAME }}\n DB_USER=${{ secrets.DB_USER }}\n DB_PASSWORD=${{ secrets.DB_PASSWORD }}\n\n # Парсеры\n PARSER_REQUEST_DELAY=${{ vars.PARSER_REQUEST_DELAY }}\n PARSER_MAX_RETRIES=${{ vars.PARSER_MAX_RETRIES }}\n PARSER_TIMEOUT=${{ vars.PARSER_TIMEOUT }}\n\n # Логирование\n LOG_LEVEL=${{ vars.LOG_LEVEL }}\n LOG_DIR=${{ vars.LOG_DIR }}\n\n # Телеграм\n TELEGRAM_BOT_TOKEN=${{ secrets.TELEGRAM_BOT_TOKEN }}\n ADMIN_CHAT_ID=${{ secrets.ADMIN_CHAT_ID }}\n PROXY_URL=${{ secrets.PROXY_URL }}\n\n # Время\n TIMEZONE=${{ vars.TIMEZONE }}\n EOF\n \n echo \"✅ .env file created at /home/newsapp/news_graph\"\n\n - name: Deploy with Docker Compose\n run: |\n cd /home/newsapp/news_graph\n \n docker-compose down\n docker-compose up -d --build\n docker image prune -f\n \n echo \"✅ Deployment completed\"\n\n - name: Check container status\n run: |\n cd /home/newsapp/news_graph\n docker-compose ps"}, "56943b65e1ccba5b87024d03493e6472281d448d": {"t": 1785753415.669458, "n": 1836, "h": "af9fe3dd4c4d80da", "c": "name: Deploy to VPS\n\non:\n push:\n branches: [ \"main\" ]\n workflow_dispatch:\n\njobs:\n deploy:\n if: \"!contains(github.event.head_commit.message, '[skip ci]')\"\n runs-on: self-hosted\n steps:\n - name: Checkout code\n uses: actions/checkout@v4\n\n - name: Sync code to project directory\n run: | \n # Синхронизируем в реальную папку проекта\n rsync -av --delete --exclude='.git' \\\n ${{ github.workspace }}/ \\\n /home/newsapp/news_graph/\n\n - name: Create .env file\n run: |\n cd /home/newsapp/news_graph\n \n cat > .env << 'EOF'\n # База данных\n DB_HOST=${{ secrets.DB_HOST }}\n DB_PORT=${{ secrets.DB_PORT }}\n DB_NAME=${{ secrets.DB_NAME }}\n DB_USER=${{ secrets.DB_USER }}\n DB_PASSWORD=${{ secrets.DB_PASSWORD }}\n\n # Парсеры\n PARSER_REQUEST_DELAY=${{ vars.PARSER_REQUEST_DELAY }}\n PARSER_MAX_RETRIES=${{ vars.PARSER_MAX_RETRIES }}\n PARSER_TIMEOUT=${{ vars.PARSER_TIMEOUT }}\n\n # Логирование\n LOG_LEVEL=${{ vars.LOG_LEVEL }}\n LOG_DIR=${{ vars.LOG_DIR }}\n\n # Телеграм\n TELEGRAM_BOT_TOKEN=${{ secrets.TELEGRAM_BOT_TOKEN }}\n ADMIN_CHAT_ID=${{ secrets.ADMIN_CHAT_ID }}\n\n # Время\n TIMEZONE=${{ vars.TIMEZONE }}\n EOF\n \n echo \"✅ .env file created at /home/newsapp/news_graph\"\n\n - name: Deploy with Docker Compose\n run: |\n cd /home/newsapp/news_graph\n \n docker-compose down\n docker-compose up -d --build\n docker image prune -f\n \n echo \"✅ Deployment completed\"\n\n - name: Check container status\n run: |\n cd /home/newsapp/news_graph\n docker-compose ps"}, "afc3dc8e44d5e411f632188ad0c38a45274e3d01": {"t": 1785753417.279488, "n": 1769, "h": "010782d5d445e93e", "c": "name: Deploy to VPS\n\non:\n push:\n branches: [ \"main\" ]\n workflow_dispatch:\n\njobs:\n deploy:\n runs-on: self-hosted\n steps:\n - name: Checkout code\n uses: actions/checkout@v4\n\n - name: Sync code to project directory\n run: | \n # Синхронизируем в реальную папку проекта\n rsync -av --delete --exclude='.git' \\\n ${{ github.workspace }}/ \\\n /home/newsapp/news_graph/\n\n - name: Create .env file\n run: |\n cd /home/newsapp/news_graph\n \n cat > .env << 'EOF'\n # База данных\n DB_HOST=${{ secrets.DB_HOST }}\n DB_PORT=${{ secrets.DB_PORT }}\n DB_NAME=${{ secrets.DB_NAME }}\n DB_USER=${{ secrets.DB_USER }}\n DB_PASSWORD=${{ secrets.DB_PASSWORD }}\n\n # Парсеры\n PARSER_REQUEST_DELAY=${{ vars.PARSER_REQUEST_DELAY }}\n PARSER_MAX_RETRIES=${{ vars.PARSER_MAX_RETRIES }}\n PARSER_TIMEOUT=${{ vars.PARSER_TIMEOUT }}\n\n # Логирование\n LOG_LEVEL=${{ vars.LOG_LEVEL }}\n LOG_DIR=${{ vars.LOG_DIR }}\n\n # Телеграм\n TELEGRAM_BOT_TOKEN=${{ secrets.TELEGRAM_BOT_TOKEN }}\n ADMIN_CHAT_ID=${{ secrets.ADMIN_CHAT_ID }}\n\n # Время\n TIMEZONE=${{ vars.TIMEZONE }}\n EOF\n \n echo \"✅ .env file created at /home/newsapp/news_graph\"\n\n - name: Deploy with Docker Compose\n run: |\n cd /home/newsapp/news_graph\n \n docker-compose down\n docker-compose up -d --build\n docker image prune -f\n \n echo \"✅ Deployment completed\"\n\n - name: Check container status\n run: |\n cd /home/newsapp/news_graph\n docker-compose ps"}, "4a313d20f8a7bf49ea65c00015fada7450b74e46": {"t": 1785753419.7061648, "n": 1757, "h": "c02a9086d15abe7b", "c": "name: Deploy to VPS\n\non:\n push:\n branches: [ \"main\" ]\n workflow_dispatch:\n\njobs:\n deploy:\n runs-on: self-hosted\n steps:\n - name: Checkout code\n uses: actions/checkout@v4\n\n - name: Sync code to project directory\n run: | \n # Синхронизируем в реальную папку проекта\n rsync -av --delete --exclude='.git' \\\n ${{ github.workspace }}/ \\\n /home/newsapp/news_graph/\n\n - name: Create .env file\n run: |\n cd /home/newsapp/news_graph\n \n cat > .env << 'EOF'\n # База данных\n DB_HOST=${{ vars.DB_HOST }}\n DB_PORT=${{ vars.DB_PORT }}\n DB_NAME=${{ vars.DB_NAME }}\n DB_USER=${{ vars.DB_USER }}\n DB_PASSWORD=${{ secrets.DB_PASSWORD }}\n\n # Парсеры\n PARSER_REQUEST_DELAY=${{ vars.PARSER_REQUEST_DELAY }}\n PARSER_MAX_RETRIES=${{ vars.PARSER_MAX_RETRIES }}\n PARSER_TIMEOUT=${{ vars.PARSER_TIMEOUT }}\n\n # Логирование\n LOG_LEVEL=${{ vars.LOG_LEVEL }}\n LOG_DIR=${{ vars.LOG_DIR }}\n\n # Телеграм\n TELEGRAM_BOT_TOKEN=${{ secrets.TELEGRAM_BOT_TOKEN }}\n ADMIN_CHAT_ID=${{ secrets.ADMIN_CHAT_ID }}\n\n # Время\n TIMEZONE=${{ vars.TIMEZONE }}\n EOF\n \n echo \"✅ .env file created at /home/newsapp/news_graph\"\n\n - name: Deploy with Docker Compose\n run: |\n cd /home/newsapp/news_graph\n \n docker-compose down\n docker-compose up -d --build\n docker image prune -f\n \n echo \"✅ Deployment completed\"\n\n - name: Check container status\n run: |\n cd /home/newsapp/news_graph\n docker-compose ps"}, "e8f7c88d43bb7038af2fe699f307110755d950ef": {"t": 1785753423.9831767, "n": 2026, "h": "a2e89c4f0141751f", "c": "name: Deploy to VPS\n\non:\n push:\n branches: [ \"main\" ]\n workflow_dispatch: # позволяет запускать вручную из интерфейса GitHub\n\njobs:\n deploy:\n runs-on: self-hosted\n steps:\n - name: Checkout code\n uses: actions/checkout@v4\n with:\n path: /tmp/workspace # Временная директория для свежего кода\n\n - name: Sync code to project directory\n run: |\n # Синхронизируем код из временной папки в реальный проект\n # --delete удаляет файлы, которых больше нет в репозитории\n rsync -av --delete --exclude='.git' \\\n /tmp/workspace/ \\\n /home/newsapp/news_graph/\n\n - name: Create .env file\n run: |\n cd /home/newsapp/news_graph\n \n cat > .env << 'EOF'\n # База данных\n DB_HOST=${{ vars.DB_HOST }}\n DB_PORT=${{ vars.DB_PORT }}\n DB_NAME=${{ vars.DB_NAME }}\n DB_USER=${{ vars.DB_USER }}\n DB_PASSWORD=${{ secrets.DB_PASSWORD }}\n\n # Парсеры\n PARSER_REQUEST_DELAY=${{ vars.PARSER_REQUEST_DELAY }}\n PARSER_MAX_RETRIES=${{ vars.PARSER_MAX_RETRIES }}\n PARSER_TIMEOUT=${{ vars.PARSER_TIMEOUT }}\n\n # Логирование\n LOG_LEVEL=${{ vars.LOG_LEVEL }}\n LOG_DIR=${{ vars.LOG_DIR }}\n\n # Телеграм\n TELEGRAM_BOT_TOKEN=${{ secrets.TELEGRAM_BOT_TOKEN }}\n ADMIN_CHAT_ID=${{ secrets.ADMIN_CHAT_ID }}\n\n # Время\n TIMEZONE=${{ vars.TIMEZONE }}\n EOF\n \n echo \"✅ .env file created\"\n\n - name: Deploy with Docker Compose\n run: |\n cd /home/newsapp/news_graph\n \n # Собираем и перезапускаем контейнеры\n docker-compose down\n docker-compose up -d --build\n \n # Чистим старые образы\n docker image prune -f\n \n echo \"✅ Deployment completed\"\n\n - name: Check container status\n run: |\n cd /home/newsapp/news_graph\n docker-compose ps"}}}}