From f8ab142d663f25b6925ded4df599d02477726823 Mon Sep 17 00:00:00 2001 From: Anna_Putilovskaya Date: Sun, 18 Oct 2026 19:18:11 +0300 Subject: [PATCH 1/8] docs: add MIT license --- LICENSE | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 LICENSE diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..d126f82 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2026 Anna Putilovskaya + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. From 6fef47334d2871bf54489cc1281715cdf60ec286 Mon Sep 17 00:00:00 2001 From: Anna_Putilovskaya Date: Sun, 18 Oct 2026 19:22:18 +0300 Subject: [PATCH 2/8] ci: add ruff + pytest workflow with self-generated coverage badge --- .github/workflows/ci.yml | 75 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 75 insertions(+) create mode 100644 .github/workflows/ci.yml diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..e281fd3 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,75 @@ +name: CI + +on: + push: + branches: [main] + pull_request: + +jobs: + test: + runs-on: ubuntu-latest + services: + postgres: + image: postgres:17 + env: + POSTGRES_USER: test + POSTGRES_PASSWORD: test + POSTGRES_DB: test + ports: + - 5432:5432 + options: >- + --health-cmd "pg_isready -U test" + --health-interval 10s + --health-timeout 5s + --health-retries 5 + env: + SECRET_KEY: ci-secret-key + DEBUG: "False" + ALLOWED_HOSTS: localhost,127.0.0.1 + POSTGRES_DB: test + POSTGRES_USER: test + POSTGRES_PASSWORD: test + POSTGRES_HOST: localhost + POSTGRES_PORT: "5432" + CELERY_BROKER_URL: amqp://guest:guest@localhost:5672// + CELERY_RESULT_BACKEND: rpc:// + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-python@v5 + with: + python-version: "3.12" + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install -r requirements.txt -r requirements-dev.txt + - name: Migrate + run: python manage.py migrate + - name: Lint + run: ruff check . + - name: Format check + run: ruff format --check . + - name: Tests + run: pytest --cov --cov-report=term-missing --cov-fail-under=55 + - name: Coverage badge data + id: coverage + run: | + coverage json -o coverage.json + python - <<'PY' >> "$GITHUB_OUTPUT" + import json + + totals = json.load(open("coverage.json"))["totals"] + percent = totals["percent_covered"] + color = "green" if percent >= 80 else "yellow" if percent >= 55 else "red" + print(f"total={totals['percent_covered_display']}%") + print(f"color={color}") + PY + - name: Update coverage badge + uses: schneegans/dynamic-badges-action@v1.7.0 + continue-on-error: true + with: + auth: ${{ secrets.GIST_TOKEN }} + gistID: + filename: coverage.json + label: coverage + message: ${{ steps.coverage.outputs.total }} + color: ${{ steps.coverage.outputs.color }} From 648ff2c459f8c69a66180381462cad4fb87f2ead Mon Sep 17 00:00:00 2001 From: Anna_Putilovskaya Date: Sun, 18 Oct 2026 19:26:00 +0300 Subject: [PATCH 3/8] docs: add Russian README per standard template --- README.ru.md | 127 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 127 insertions(+) create mode 100644 README.ru.md diff --git a/README.ru.md b/README.ru.md new file mode 100644 index 0000000..679d26c --- /dev/null +++ b/README.ru.md @@ -0,0 +1,127 @@ +# Сервис уведомлений + +[![CI](https://github.com/annaputilovskaya/NotificationService/actions/workflows/ci.yml/badge.svg)](https://github.com/annaputilovskaya/NotificationService/actions/workflows/ci.yml) +[![coverage](https://img.shields.io/endpoint?url=https://gist.githubusercontent.com///raw/coverage.json)](https://github.com/annaputilovskaya/NotificationService/actions/workflows/ci.yml) +[![License](https://img.shields.io/badge/License-MIT-yellow.svg)](./LICENSE) +[![Python](https://img.shields.io/badge/python-3.12-blue.svg)](https://www.python.org/) + +API для надёжной доставки уведомлений пользователям по нескольким каналам — Email, SMS и Telegram — с автоматическим переключением между каналами при сбое. + +[English version](README.md) + +## Возможности + +- **Пользователи** — регистрация по email и паролю, получение и обновление JWT-токена авторизации. + + Пользователи + +- **Получатели** — создание, просмотр, изменение и удаление получателей. + + Получатели + +- **Уведомления** — создание уведомления, просмотр одного уведомления и списка. + + Уведомления + +- **Надёжная доставка (failover)** — при создании уведомления ставится задача Celery. Стратегии перебираются по порядку **Email → Telegram → SMS**: если канал не сработал, используется следующий. Если ни одна стратегия не сработала для части получателей, попытка повторяется для этих получателей через заданный интервал. +- **Пагинация** — эндпоинты списков отдают постраничный ответ `{"count": ..., "results": [...]}` (параметр `?page=`). +- **Логирование** — логи отправки пишутся в консоль и сохраняются в файл. +- Сервис ориентирован на разовые уведомления; поддержка повторяющихся уведомлений — возможная доработка. + +## Стек + +- Python 3.12 +- Django REST Framework (Django ORM) +- PostgreSQL 17 +- Celery + RabbitMQ +- drf-yasg (Swagger/Redoc) + +## Архитектура + +```mermaid +flowchart LR + Client[Клиент] --> API[Django REST API] + API -->|создание уведомления| Task[Celery: send_notification] + Task --> Email[Email strategy] + Email -->|fail| Telegram[Telegram strategy] + Telegram -->|fail| SMS[SMS strategy] + Email --> SMTP[(SMTP)] + Telegram --> TG[(Telegram API)] + SMS --> SMSAPI[(sms.ru)] + Task --> DB[(PostgreSQL)] +``` + +## Модель данных + +Схема базы данных + +## Начало работы + +### Требования + +- Docker и Docker Compose + +### Конфигурация + +Скопируйте `.env.example` в `.env` и заполните значения: + +```bash +cp .env.example .env +``` + +| Переменная | Описание | Пример | +|---|---|---| +| `SECRET_KEY` | Секретный ключ Django | `django-insecure-...` | +| `DEBUG` | Режим отладки | `False` | +| `ALLOWED_HOSTS` | Разрешённые хосты (через запятую) | `localhost,127.0.0.1` | +| `CORS_ALLOWED_ORIGINS` | Разрешённые CORS-origins | `http://localhost:8000` | +| `POSTGRES_*` | Параметры подключения к БД | `POSTGRES_DB=notifications` | +| `CELERY_BROKER_URL` | URL брокера Celery (RabbitMQ) | `amqp://user:pass@rabbitmq:5672//` | +| `EMAIL_*` | Настройки почтового сервера | `EMAIL_HOST=smtp.example.com` | +| `TELEGRAM_TOKEN` | Токен Telegram-бота | `123456:ABC` | +| `SMS_API_KEY` / `SMS_SENDER` | Доступ к sms.ru | `SMS_SENDER=Service` | +| `SMS_TEST` | Тестовый режим sms.ru (не отправляет СМС) | `0` | + +### Установка и запуск + +Склонируйте репозиторий и запустите сервисы: + +```bash +git clone https://github.com/annaputilovskaya/NotificationService.git +cd NotificationService +docker compose up -d --build +``` + +Проверка версии Docker (если не установлен — [инструкция](https://www.docker.com/)): + +```bash +docker --version +``` + +Интерактивная документация API (Swagger): http://localhost:8000/swagger/ + +## Тесты + +Тесты запускаются на PostgreSQL. Поднимите БД и выполните: + +```bash +docker compose up -d db +python -m pytest --cov +``` + +Внешние сервисы (SMTP, Telegram, sms.ru) в тестах замоканы. Ранее проект проверялся вручную через Postman; теперь добавлены автоматизированные тесты ядра и smoke. + +## Структура проекта + +``` +config/ # настройки Django, Celery, urls +notifications/ # уведомления: модель, стратегии, задача Celery +recipients/ # получатели +users/ # пользователи, JWT +conftest.py # общие фикстуры pytest +pyproject.toml # конфиг ruff/pytest/coverage +``` + +## Лицензия + +Проект распространяется под лицензией MIT — см. [LICENSE](LICENSE). From d05d133421582e4c54f6010d9f0c14fdbdd666c7 Mon Sep 17 00:00:00 2001 From: Anna_Putilovskaya Date: Sun, 18 Oct 2026 19:29:36 +0300 Subject: [PATCH 4/8] docs: rewrite README (EN) per standard template --- README.md | 238 +++++++++++++++++++++++++++++------------------------- 1 file changed, 127 insertions(+), 111 deletions(-) diff --git a/README.md b/README.md index dd6d768..90081eb 100644 --- a/README.md +++ b/README.md @@ -1,111 +1,127 @@ -# Сервис уведомлений - -### Задача: - -Создать сервис для отправки уведомлений пользователям. -Пользователь может получать уведомления через: -- Email -- SMS -- Telegram - -Нужно обеспечить надежную доставку - если один способ не сработал, попробовать другой. - ---- -### Технический требования: - -- Язык программирования: - - Python 3.12 -- Фреймворк: - - Django Rest Framework -- ORM: - - Django ORM -- Хранение данных: - - PostgreSQL 17 -- Очереди сообщений: - - Celery + RabbitMQ - ---- -### Запуск проекта: - -- Клонируйте проект: - ```bash - git clone https://github.com/annaputilovskaya/NotificationService.git - ``` -- Создайте файл .env в корне проекта по образцу (Файл .env.example) и заполните его данными. - - Команда для Unix-подобных систем для копирования файла: - ```bash - cp .env.example .env - ``` -- Убедитесь, что на Вашем устройстве установлен Docker. - - Проверка версии Docker: - ```bash - docker --version - ``` - Если докер не установлен, [осуществите его установку по инструкции с официального сайта](https://www.docker.com/) - -- Осуществите сборку образов и запуск контейнеров: - ```bash - docker compose up -d --build - ``` - ---- -### Структура базы данных: - -image - ---- -### Функционал и бизнес-логика - -1. Пользователи: - - Регистрация с email и паролем - - Получение токена для авторизация - - ОБновление токена авторизации - -image - - - -2. Получатели - - Создание получателя - - Просмотр получателя - - Просмотр списка получателей - - Изменение получтаеля - - Удаление получателя - -image - - - -3. Уведомления - - Создание уведомления - - Просмотр уведомления - - Просмотр списка уведомлений - -image - - - При создания уведомления создается задача по отправке уведомления. - Происходит выбор стратегии отпраки уведомления в следующем порядке: - - Email - - Telegram - - SMS - - В случае, если первая стратегия не сработала, переходим ко второй, далее к третьей. - Если ни одна страегия отправки для каких-либо получателей не сработала, повторяем попытку отправки для этих получателей через определенный временной интервал. - - --- -Сервис подходит для отправки разовых уведомлений. Если нужны повторяющиеся уведомления, возможна доработка. - -В проекте настроено логирование отправки уведомлений с отправкой логов в консоль и с сохранением в файл. - -Проведено ручное тестирование проекта с помощью Postman. - -Автоматизированное тестирование не проводилось. - -Для более подробной информации об API ознакомьтесь с интерактивной документацией. - -Если проект запущен на localhost, пройдите по [ссылке](http://localhost:8000/swagger/) - - +# Notification Service + +[![CI](https://github.com/annaputilovskaya/NotificationService/actions/workflows/ci.yml/badge.svg)](https://github.com/annaputilovskaya/NotificationService/actions/workflows/ci.yml) +[![coverage](https://img.shields.io/endpoint?url=https://gist.githubusercontent.com///raw/coverage.json)](https://github.com/annaputilovskaya/NotificationService/actions/workflows/ci.yml) +[![License](https://img.shields.io/badge/License-MIT-yellow.svg)](./LICENSE) +[![Python](https://img.shields.io/badge/python-3.12-blue.svg)](https://www.python.org/) + +An API for reliable delivery of notifications to users across multiple channels — Email, SMS, and Telegram — with automatic failover between channels. + +[Русская версия](README.ru.md) + +## Features + +- **Users** — email/password sign-up, issuing and refreshing JWT access tokens. + + Users + +- **Recipients** — create, view, update, and delete recipients. + + Recipients + +- **Notifications** — create a notification, view a single notification and a list. + + Notifications + +- **Reliable delivery (failover)** — creating a notification enqueues a Celery task. Strategies are tried in order **Email → Telegram → SMS**: when a channel fails, the next one is used. If no strategy succeeds for some recipients, delivery to those recipients is retried after a delay. +- **Pagination** — list endpoints return a paginated `{"count": ..., "results": [...]}` payload (the `?page=` parameter). +- **Logging** — delivery logs go to the console and to a file. +- The service targets one-off notifications; recurring notifications are a possible extension. + +## Stack + +- Python 3.12 +- Django REST Framework (Django ORM) +- PostgreSQL 17 +- Celery + RabbitMQ +- drf-yasg (Swagger/Redoc) + +## Architecture + +```mermaid +flowchart LR + Client[Client] --> API[Django REST API] + API -->|create notification| Task[Celery: send_notification] + Task --> Email[Email strategy] + Email -->|fail| Telegram[Telegram strategy] + Telegram -->|fail| SMS[SMS strategy] + Email --> SMTP[(SMTP)] + Telegram --> TG[(Telegram API)] + SMS --> SMSAPI[(sms.ru)] + Task --> DB[(PostgreSQL)] +``` + +## Data model + +Database schema + +## Getting started + +### Prerequisites + +- Docker and Docker Compose + +### Configuration + +Copy `.env.example` to `.env` and fill in the values: + +```bash +cp .env.example .env +``` + +| Variable | Description | Example | +|---|---|---| +| `SECRET_KEY` | Django secret key | `django-insecure-...` | +| `DEBUG` | Debug mode | `False` | +| `ALLOWED_HOSTS` | Allowed hosts (comma-separated) | `localhost,127.0.0.1` | +| `CORS_ALLOWED_ORIGINS` | Allowed CORS origins | `http://localhost:8000` | +| `POSTGRES_*` | Database connection settings | `POSTGRES_DB=notifications` | +| `CELERY_BROKER_URL` | Celery broker URL (RabbitMQ) | `amqp://user:pass@rabbitmq:5672//` | +| `EMAIL_*` | Mail server settings | `EMAIL_HOST=smtp.example.com` | +| `TELEGRAM_TOKEN` | Telegram bot token | `123456:ABC` | +| `SMS_API_KEY` / `SMS_SENDER` | sms.ru credentials | `SMS_SENDER=Service` | +| `SMS_TEST` | sms.ru test mode (does not send SMS) | `0` | + +### Install & run + +Clone the repository and start the services: + +```bash +git clone https://github.com/annaputilovskaya/NotificationService.git +cd NotificationService +docker compose up -d --build +``` + +Verify Docker is installed (otherwise follow the [official instructions](https://www.docker.com/)): + +```bash +docker --version +``` + +Interactive API documentation (Swagger): http://localhost:8000/swagger/ + +## Tests + +Tests run against PostgreSQL. Start the database and run: + +```bash +docker compose up -d db +python -m pytest --cov +``` + +External services (SMTP, Telegram, sms.ru) are mocked. The project was originally checked manually via Postman; automated core and smoke tests are now in place. + +## Project structure + +``` +config/ # Django settings, Celery, urls +notifications/ # notifications: model, strategies, Celery task +recipients/ # recipients +users/ # users, JWT +conftest.py # shared pytest fixtures +pyproject.toml # ruff/pytest/coverage config +``` + +## License + +Released under the MIT License — see [LICENSE](LICENSE). From 26162aa88f922928765a0f97d2faa8c151afd090 Mon Sep 17 00:00:00 2001 From: Anna_Putilovskaya Date: Sun, 18 Oct 2026 21:25:22 +0300 Subject: [PATCH 5/8] chore: set real gist id for coverage badge --- .github/workflows/ci.yml | 2 +- README.md | 2 +- README.ru.md | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e281fd3..e66ab0a 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -68,7 +68,7 @@ jobs: continue-on-error: true with: auth: ${{ secrets.GIST_TOKEN }} - gistID: + gistID: 097e2cc0afbb6ee7fdbfb4d2d67bb87d filename: coverage.json label: coverage message: ${{ steps.coverage.outputs.total }} diff --git a/README.md b/README.md index 90081eb..5a8b5e5 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ # Notification Service [![CI](https://github.com/annaputilovskaya/NotificationService/actions/workflows/ci.yml/badge.svg)](https://github.com/annaputilovskaya/NotificationService/actions/workflows/ci.yml) -[![coverage](https://img.shields.io/endpoint?url=https://gist.githubusercontent.com///raw/coverage.json)](https://github.com/annaputilovskaya/NotificationService/actions/workflows/ci.yml) +[![coverage](https://img.shields.io/endpoint?url=https://gist.githubusercontent.com/annaputilovskaya/097e2cc0afbb6ee7fdbfb4d2d67bb87d/raw/coverage.json)](https://github.com/annaputilovskaya/NotificationService/actions/workflows/ci.yml) [![License](https://img.shields.io/badge/License-MIT-yellow.svg)](./LICENSE) [![Python](https://img.shields.io/badge/python-3.12-blue.svg)](https://www.python.org/) diff --git a/README.ru.md b/README.ru.md index 679d26c..1c86fa8 100644 --- a/README.ru.md +++ b/README.ru.md @@ -1,7 +1,7 @@ # Сервис уведомлений [![CI](https://github.com/annaputilovskaya/NotificationService/actions/workflows/ci.yml/badge.svg)](https://github.com/annaputilovskaya/NotificationService/actions/workflows/ci.yml) -[![coverage](https://img.shields.io/endpoint?url=https://gist.githubusercontent.com///raw/coverage.json)](https://github.com/annaputilovskaya/NotificationService/actions/workflows/ci.yml) +[![coverage](https://img.shields.io/endpoint?url=https://gist.githubusercontent.com/annaputilovskaya/097e2cc0afbb6ee7fdbfb4d2d67bb87d/raw/coverage.json)](https://github.com/annaputilovskaya/NotificationService/actions/workflows/ci.yml) [![License](https://img.shields.io/badge/License-MIT-yellow.svg)](./LICENSE) [![Python](https://img.shields.io/badge/python-3.12-blue.svg)](https://www.python.org/) From b898beccb2db2dda6f6eb3e2083ff1dc9f384347 Mon Sep 17 00:00:00 2001 From: Anna_Putilovskaya Date: Sun, 18 Oct 2026 21:35:05 +0300 Subject: [PATCH 6/8] fix: quote mermaid node labels for GitHub rendering --- README.md | 18 +++++++++--------- README.ru.md | 18 +++++++++--------- 2 files changed, 18 insertions(+), 18 deletions(-) diff --git a/README.md b/README.md index 5a8b5e5..a482725 100644 --- a/README.md +++ b/README.md @@ -40,15 +40,15 @@ An API for reliable delivery of notifications to users across multiple channels ```mermaid flowchart LR - Client[Client] --> API[Django REST API] - API -->|create notification| Task[Celery: send_notification] - Task --> Email[Email strategy] - Email -->|fail| Telegram[Telegram strategy] - Telegram -->|fail| SMS[SMS strategy] - Email --> SMTP[(SMTP)] - Telegram --> TG[(Telegram API)] - SMS --> SMSAPI[(sms.ru)] - Task --> DB[(PostgreSQL)] + Client["Client"] --> API["Django REST API"] + API -->|create notification| Task["Celery: send_notification"] + Task --> Email["Email strategy"] + Email -->|fail| Telegram["Telegram strategy"] + Telegram -->|fail| SMS["SMS strategy"] + Email --> SMTP[("SMTP")] + Telegram --> TG[("Telegram API")] + SMS --> SMSAPI[("sms.ru")] + Task --> DB[("PostgreSQL")] ``` ## Data model diff --git a/README.ru.md b/README.ru.md index 1c86fa8..c415be4 100644 --- a/README.ru.md +++ b/README.ru.md @@ -40,15 +40,15 @@ API для надёжной доставки уведомлений пользо ```mermaid flowchart LR - Client[Клиент] --> API[Django REST API] - API -->|создание уведомления| Task[Celery: send_notification] - Task --> Email[Email strategy] - Email -->|fail| Telegram[Telegram strategy] - Telegram -->|fail| SMS[SMS strategy] - Email --> SMTP[(SMTP)] - Telegram --> TG[(Telegram API)] - SMS --> SMSAPI[(sms.ru)] - Task --> DB[(PostgreSQL)] + Client["Клиент"] --> API["Django REST API"] + API -->|создание уведомления| Task["Celery: send_notification"] + Task --> Email["Email strategy"] + Email -->|fail| Telegram["Telegram strategy"] + Telegram -->|fail| SMS["SMS strategy"] + Email --> SMTP[("SMTP")] + Telegram --> TG[("Telegram API")] + SMS --> SMSAPI[("sms.ru")] + Task --> DB[("PostgreSQL")] ``` ## Модель данных From 7a04d83d32899fa524f6a8502ce754b91e2777d8 Mon Sep 17 00:00:00 2001 From: Anna_Putilovskaya Date: Sun, 18 Oct 2026 22:01:36 +0300 Subject: [PATCH 7/8] docs: update notifications screenshot in READMEs --- README.md | 2 +- README.ru.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index a482725..6d052eb 100644 --- a/README.md +++ b/README.md @@ -21,7 +21,7 @@ An API for reliable delivery of notifications to users across multiple channels - **Notifications** — create a notification, view a single notification and a list. - Notifications + Notifications - **Reliable delivery (failover)** — creating a notification enqueues a Celery task. Strategies are tried in order **Email → Telegram → SMS**: when a channel fails, the next one is used. If no strategy succeeds for some recipients, delivery to those recipients is retried after a delay. - **Pagination** — list endpoints return a paginated `{"count": ..., "results": [...]}` payload (the `?page=` parameter). diff --git a/README.ru.md b/README.ru.md index c415be4..b0db662 100644 --- a/README.ru.md +++ b/README.ru.md @@ -21,7 +21,7 @@ API для надёжной доставки уведомлений пользо - **Уведомления** — создание уведомления, просмотр одного уведомления и списка. - Уведомления + Уведомления - **Надёжная доставка (failover)** — при создании уведомления ставится задача Celery. Стратегии перебираются по порядку **Email → Telegram → SMS**: если канал не сработал, используется следующий. Если ни одна стратегия не сработала для части получателей, попытка повторяется для этих получателей через заданный интервал. - **Пагинация** — эндпоинты списков отдают постраничный ответ `{"count": ..., "results": [...]}` (параметр `?page=`). From 97b9b88c32fdfd1c01ad0762dc099faad5b06e0e Mon Sep 17 00:00:00 2001 From: Anna_Putilovskaya Date: Sun, 18 Oct 2026 22:17:43 +0300 Subject: [PATCH 8/8] fix: create logs directory on settings import for fresh checkouts --- config/settings.py | 1 + 1 file changed, 1 insertion(+) diff --git a/config/settings.py b/config/settings.py index 0806c51..aca1454 100644 --- a/config/settings.py +++ b/config/settings.py @@ -178,6 +178,7 @@ # Logging LOG_FILE_NAME = "notifications.log" LOG_FILE_PATH = os.path.join(BASE_DIR, "logs", LOG_FILE_NAME) +os.makedirs(os.path.dirname(LOG_FILE_PATH), exist_ok=True) LOGGING = { "version": 1,