Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
390 changes: 278 additions & 112 deletions docs/modules/chats.md

Large diffs are not rendered by default.

232 changes: 231 additions & 1 deletion docs/modules/core.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,233 @@
# Core

TODO
## Назначение

Модуль `core` содержит общие сущности и инфраструктурные helper'ы, которые
переиспользуются другими доменными модулями Procollab.

В модуле находятся:

- generic-модели лайков, просмотров и ссылок;
- справочники навыков и специализаций;
- generic-связи навыков и специализаций с объектами;
- REST endpoints справочника навыков;
- общие serializers, permissions и pagination;
- helpers для Excel-выгрузок;
- cache-ключи онлайна пользователей;
- WebSocket JWT middleware;
- logging middleware.

## Статус модуля

`core` подключен в публичный API через `/core/`, но публичная API-поверхность
сейчас ограничена endpoints навыков.

Модуль является shared-слоем: изменения в нем могут затронуть `users`,
`projects`, `news`, `feed`, `vacancy`, `partner_programs`, `courses`,
`project_rates`, `metrics` и `chats`.

Собственных тестов у `core` сейчас нет. Часть поведения косвенно покрывается
тестами зависимых модулей.

## Основные возможности

- хранение generic-лайков через `Like`;
- хранение generic-просмотров через `View`;
- хранение generic-ссылок через `Link`;
- справочник навыков `SkillCategory` / `Skill`;
- generic-привязка навыков через `SkillToObject`;
- справочник специализаций `SpecializationCategory` / `Specialization`;
- generic-привязка специализаций через `SpecializationToObject`;
- получение навыков nested-списком по категориям;
- получение навыков плоским paginated-списком с фильтром по названию;
- подготовка XLSX-файлов в памяти;
- безопасная подготовка имени файла и значений Excel-ячеек;
- построение download-response для XLSX;
- формирование ключей online-cache;
- JWT-аутентификация WebSocket через subprotocol;
- перехват стандартного logging в loguru.

## Архитектура

- `core/models.py` - generic-модели, навыки и специализации.
- `core/views.py` - API справочника навыков.
- `core/serializers.py` - serializers навыков и общие request serializers.
- `core/services.py` - лайки, просмотры, ссылки и Base64 image encoder.
- `core/utils.py` - email helper, online-cache keys и Excel helpers.
- `core/permissions.py` - общие permissions.
- `core/pagination.py` - общий limit/offset pagination.
- `core/filters.py` - фильтр навыков.
- `core/fields.py` - кастомное поле списка для comma-separated значений.
- `core/auth/middleware.py` - WebSocket JWT auth middleware.
- `core/log/` - интеграция стандартного logging с loguru.
- `core/admin.py` - Django admin для core-сущностей.

## Ключевые сущности

- `Like` - generic-лайк пользователя к объекту через `ContentType`.
- `View` - generic-просмотр пользователя к объекту через `ContentType`.
- `Link` - generic-ссылка, привязанная к объекту через `ContentType`.
- `SkillCategory` - категория навыка.
- `Skill` - навык внутри категории.
- `SkillToObject` - generic-связь навыка с пользователем, вакансией, проектом
или другим объектом.
- `SpecializationCategory` - категория специализации.
- `Specialization` - специализация внутри категории.
- `SpecializationToObject` - generic-связь специализации с объектом.

## API

- `GET /core/skills/nested/` - категории навыков со вложенным списком навыков.
- `GET /core/skills/inline/` - плоский список навыков с pagination.

Фильтр для `/core/skills/inline/`:

- `name__icontains` - поиск навыка по части названия.

Pagination:

- `limit`, по умолчанию `10`;
- `offset`.

Справочник специализаций физически хранится в `core`, но endpoints находятся в
модуле `users`:

- `GET /auth/users/specializations/nested/`;
- `GET /auth/users/specializations/inline/`.

## Основные сценарии

### 1. Фронт получает справочник навыков

Для отображения навыков по категориям используется:

```text
GET /core/skills/nested/
```

Для поиска и autocomplete используется:

```text
GET /core/skills/inline/?name__icontains=python
```

### 2. Модуль привязывает навыки к объекту

Доменные модули создают `SkillToObject` через `ContentType`.

Например:

- `users` хранит навыки пользователя;
- `vacancy` хранит требуемые навыки вакансии;
- serializers используют `SkillToObjectSerializer` для единого response
формата навыка.

### 3. Модуль фиксирует лайк или просмотр

`core.services` предоставляет функции:

- `set_like(obj, user, is_liked)`;
- `add_like(obj, user)`;
- `remove_like(obj, user)`;
- `is_fan(obj, user)`;
- `get_likes_count(obj)`;
- `set_viewed(obj, user, is_viewed)`;
- `add_view(obj, user)`;
- `remove_view(obj, user)`;
- `is_viewer(obj, user)`;
- `get_views_count(obj)`.

Эти функции используются в `news`, `feed`, `partner_programs`,
`project_rates` и других местах, где нужен generic-счетчик.

Важно: не все лайки в проекте уже переведены на generic-модель `core.Like`.
Например, у проектов и мероприятий еще есть отдельные legacy-модели лайков.

### 4. Модуль формирует XLSX-выгрузку

Для выгрузок используются:

- `XlsxFileToExport`;
- `sanitize_excel_value`;
- `build_xlsx_download_response`.

Эти helpers применяются в `partner_programs`, `project_rates`, `courses`,
`users` и `vacancy`.

### 5. WebSocket подключение проходит JWT-аутентификацию

`TokenAuthMiddleware` подключен в `procollab/asgi.py`.

Он ожидает WebSocket subprotocols в формате:

```text
["Bearer", "<JWT>"]
```

После проверки JWT middleware записывает пользователя в `scope["user"]`.
Этим пользуется `chats.ChatConsumer`.

### 6. Чаты обновляют online-cache

`core.utils` содержит функции:

- `get_user_online_cache_key(user)`;
- `get_users_online_cache_key()`.

`chats` пишет в эти ключи при подключении и отключении пользователя, а
`metrics` читает aggregate-ключ для отображения количества пользователей онлайн.

## Связи с другими модулями

- `users` - навыки, специализации, online-флаги, Excel-выгрузки и permissions.
- `vacancy` - required skills через `SkillToObject`, admin inline и выгрузки.
- `projects` - общие serializers/permissions, счетчики просмотров, online
данные пользователей.
- `news` - generic likes/views.
- `feed` - generic likes/views для записей ленты.
- `partner_programs` - generic likes/views и Excel-выгрузки.
- `project_rates` - счетчики просмотров проектов и выгрузки.
- `courses` - Excel-выгрузка результатов.
- `chats` - WebSocket auth и online-cache keys.
- `metrics` - чтение online-cache.
- `industries` и `events` - переиспользуют общие permissions.

## Ограничения и риски

- У `core` нет собственных тестов; shared-поведение проверяется в основном
косвенно через другие модули.
- `remove_link()` в `core.services` фильтрует `Like`, а не `Link`; это выглядит
как баг.
- `get_views_count()` кеширует значение, но `add_view()` / `remove_view()` не
инвалидируют кеш.
- `get_likes_count()` не использует кеш, хотя `LIKES_CACHING_TIMEOUT` объявлен.
- `Skill`, `SkillCategory`, `Specialization` и `SpecializationCategory` не имеют
уникальности по `name`.
- `SkillToObject` и `SpecializationToObject` не ограничивают дубли на уровне
модели.
- `Base64ImageEncoder.get_encoded_base64_from_url()` использует `urlopen` без
timeout.
- `TokenAuthentication.authenticate()` не обрабатывает отсутствие пользователя
после декодирования JWT.
- `CustomLoguruMiddleware` пишет логи в директорию `log/` внутри `BASE_DIR`;
окружение должно гарантировать доступность этой директории.
- `CustomListField` преобразует список в строку через запятую и обратно; формат
подходит не для всех типов значений.

## Тесты

Собственных тестов у модуля сейчас нет:

```text
DEBUG=True .venv/bin/python manage.py test core
```

Текущий запуск находит `0` тестов.

Поведение `core` частично покрывается тестами зависимых модулей:

- `news` и `feed` проверяют generic likes/views;
- `vacancy` и `users` проверяют работу навыков;
- `metrics` проверяет online-cache keys;
- `partner_programs`, `project_rates`, `courses` проверяют Excel-выгрузки через
общие helpers.
138 changes: 137 additions & 1 deletion docs/modules/mailing.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,139 @@
# Mailing

TODO
## Назначение

Модуль `mailing` отвечает за email-рассылки и шаблоны писем: хранение схем
старой админской формы рассылки, подготовку данных письма, отправку сообщений
через email backend и автоматические сценарии рассылок по партнерским
программам.

## Статус модуля

Модуль используется в рабочих сценариях, но не подключен как публичный API.
Основные активные точки использования:

- celery-задача `run_program_mailings`;
- старая форма рассылки из админки партнерских программ;
- общие helper-функции отправки писем, которые используют другие модули.

## Основные возможности

- хранение схемы письма в `MailingSchema`;
- рендеринг старой админской формы рассылки;
- подготовка данных письма из формы или typed dataclass;
- массовая отправка писем по строковому шаблону;
- массовая отправка писем по Django template;
- группировка писем батчами;
- сценарные рассылки участникам партнерских программ;
- логирование результата сценарных рассылок в `MailingScenarioLog`.

## Архитектура

- `mailing/models.py` - модели схем писем и логов сценарных рассылок.
- `mailing/utils.py` - подготовка данных письма и низкоуровневые функции
отправки.
- `mailing/scenarios.py` - декларативное описание сценариев рассылки по
программам.
- `mailing/tasks.py` - celery-задача запуска сценариев.
- `mailing/rendering.py` - подстановка базовых placeholders в темы и тексты.
- `mailing/views.py` - старые views для формы рассылки; сейчас не подключены в
публичный URLConf.
- `mailing/urls.py` - старые routes формы рассылки, не подключенные в
`procollab/urls.py`.
- `mailing/tests/` - regression-тесты моделей, rendering/helpers и сценариев.

## Ключевые сущности

- `MailingSchema` - схема шаблона письма и HTML-шаблон для старой формы
рассылки.
- `MailingScenarioLog` - лог отправки сценарного письма конкретному участнику
программы за конкретную дату.
- `Scenario` - dataclass с кодом сценария, триггером, правилом выбора
получателей, шаблоном и builder-контекстом.
- `EmailDataToPrepare` - typed input для подготовки данных письма из кода.

## API и внешние точки входа

Публичных endpoints модуля `mailing` сейчас нет: `mailing.urls` не подключен в
корневой `procollab/urls.py`.

Связанные внешние точки:

- `/anymail/` - webhook routes библиотеки Anymail;
- админка партнерской программы вызывает `MailingTemplateRender` напрямую через
custom admin view;
- celery beat запускает `mailing.tasks.run_program_mailings` каждый день в
10:00.

## Основные сценарии

### 1. Сценарная рассылка по партнерским программам

`run_program_mailings()` проходит по сценариям из `SCENARIOS`.

Для каждого сценария:

- вычисляется целевая дата;
- выбираются программы по дате регистрации, окончанию регистрации или дедлайну
подачи проекта;
- выбираются получатели по правилу сценария;
- создаются `MailingScenarioLog` в статусе `pending`;
- письмо отправляется через `send_mass_mail_from_template`;
- статус лога меняется на `sent` или `failed` по `anymail_status`.

Повторная отправка за ту же дату не дублирует письма со статусом `pending` или
`sent`.

### 2. Старая админская рассылка

`MailingTemplateRender` строит контекст формы:

- доступные `MailingSchema`;
- выбранные и невыбранные пользователи;
- поля шаблона из JSON-схемы.

Сейчас этот renderer используется из админки партнерских программ.

### 3. Отправка письма из других модулей

Другие модули могут подготовить `EmailDataToPrepare`, получить данные через
`prepare_mail_data()` и отправить письмо через `send_mass_mail()`.

Такой flow сейчас использует `vacancy.tasks.send_email`, который также
переиспользуется партнерскими программами и оценками проектов.

## Связи с другими модулями

- `partner_programs` - сценарные рассылки выбирают программы и участников через
selectors; админка программ использует старый renderer формы рассылки.
- `vacancy` - задачи вакансий используют mailing helpers для email-уведомлений.
- `project_rates` - переиспользует общий notification flow через
`vacancy.tasks.send_email`.
- `users` - получатели писем.
- `anymail` / Unisender Go - фактическая отправка писем в production.

## Ограничения и риски

- `mailing/urls.py` содержит старые routes, но они не подключены наружу.
- Если старые routes будут снова подключены, для них нужно отдельно проверить
permissions и безопасность массовой отправки.
- В `mailing/urls.py` есть историческая опечатка `template_fileds`; менять ее
без проверки старого UI не стоит.
- `vacancy.tasks.send_email` фактически является общим helper для уведомлений,
но находится в модуле вакансий.
- `MailingScenarioLog` пока не зарегистрирован в Django admin.

## Тесты

Текущие regression-тесты проверяют:

- строковое представление `MailingSchema` и `MailingScenarioLog`;
- подстановку placeholders в subject и template values;
- контекст старого renderer формы рассылки;
- подготовку данных письма из `EmailDataToPrepare`;
- группировку писем батчами;
- рендеринг и отправку писем по строковому шаблону;
- отправку писем по Django template с `status_callback`;
- выбор участников с неактивными аккаунтами для сценариев программ;
- успешную сценарную рассылку без повторной отправки;
- перевод сценарного лога в `failed` при ошибочном `anymail_status`.
Loading
Loading