Skip to content

Commit 944eaa1

Browse files
authored
Merge pull request #648 from PROCOLLAB-github/refactor/modules
Refactor/modules
2 parents e99ac97 + 5b5fc21 commit 944eaa1

15 files changed

Lines changed: 1269 additions & 257 deletions

docs/modules/chats.md

Lines changed: 278 additions & 112 deletions
Large diffs are not rendered by default.

docs/modules/core.md

Lines changed: 231 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,233 @@
11
# Core
22

3-
TODO
3+
## Назначение
4+
5+
Модуль `core` содержит общие сущности и инфраструктурные helper'ы, которые
6+
переиспользуются другими доменными модулями Procollab.
7+
8+
В модуле находятся:
9+
10+
- generic-модели лайков, просмотров и ссылок;
11+
- справочники навыков и специализаций;
12+
- generic-связи навыков и специализаций с объектами;
13+
- REST endpoints справочника навыков;
14+
- общие serializers, permissions и pagination;
15+
- helpers для Excel-выгрузок;
16+
- cache-ключи онлайна пользователей;
17+
- WebSocket JWT middleware;
18+
- logging middleware.
19+
20+
## Статус модуля
21+
22+
`core` подключен в публичный API через `/core/`, но публичная API-поверхность
23+
сейчас ограничена endpoints навыков.
24+
25+
Модуль является shared-слоем: изменения в нем могут затронуть `users`,
26+
`projects`, `news`, `feed`, `vacancy`, `partner_programs`, `courses`,
27+
`project_rates`, `metrics` и `chats`.
28+
29+
Собственных тестов у `core` сейчас нет. Часть поведения косвенно покрывается
30+
тестами зависимых модулей.
31+
32+
## Основные возможности
33+
34+
- хранение generic-лайков через `Like`;
35+
- хранение generic-просмотров через `View`;
36+
- хранение generic-ссылок через `Link`;
37+
- справочник навыков `SkillCategory` / `Skill`;
38+
- generic-привязка навыков через `SkillToObject`;
39+
- справочник специализаций `SpecializationCategory` / `Specialization`;
40+
- generic-привязка специализаций через `SpecializationToObject`;
41+
- получение навыков nested-списком по категориям;
42+
- получение навыков плоским paginated-списком с фильтром по названию;
43+
- подготовка XLSX-файлов в памяти;
44+
- безопасная подготовка имени файла и значений Excel-ячеек;
45+
- построение download-response для XLSX;
46+
- формирование ключей online-cache;
47+
- JWT-аутентификация WebSocket через subprotocol;
48+
- перехват стандартного logging в loguru.
49+
50+
## Архитектура
51+
52+
- `core/models.py` - generic-модели, навыки и специализации.
53+
- `core/views.py` - API справочника навыков.
54+
- `core/serializers.py` - serializers навыков и общие request serializers.
55+
- `core/services.py` - лайки, просмотры, ссылки и Base64 image encoder.
56+
- `core/utils.py` - email helper, online-cache keys и Excel helpers.
57+
- `core/permissions.py` - общие permissions.
58+
- `core/pagination.py` - общий limit/offset pagination.
59+
- `core/filters.py` - фильтр навыков.
60+
- `core/fields.py` - кастомное поле списка для comma-separated значений.
61+
- `core/auth/middleware.py` - WebSocket JWT auth middleware.
62+
- `core/log/` - интеграция стандартного logging с loguru.
63+
- `core/admin.py` - Django admin для core-сущностей.
64+
65+
## Ключевые сущности
66+
67+
- `Like` - generic-лайк пользователя к объекту через `ContentType`.
68+
- `View` - generic-просмотр пользователя к объекту через `ContentType`.
69+
- `Link` - generic-ссылка, привязанная к объекту через `ContentType`.
70+
- `SkillCategory` - категория навыка.
71+
- `Skill` - навык внутри категории.
72+
- `SkillToObject` - generic-связь навыка с пользователем, вакансией, проектом
73+
или другим объектом.
74+
- `SpecializationCategory` - категория специализации.
75+
- `Specialization` - специализация внутри категории.
76+
- `SpecializationToObject` - generic-связь специализации с объектом.
77+
78+
## API
79+
80+
- `GET /core/skills/nested/` - категории навыков со вложенным списком навыков.
81+
- `GET /core/skills/inline/` - плоский список навыков с pagination.
82+
83+
Фильтр для `/core/skills/inline/`:
84+
85+
- `name__icontains` - поиск навыка по части названия.
86+
87+
Pagination:
88+
89+
- `limit`, по умолчанию `10`;
90+
- `offset`.
91+
92+
Справочник специализаций физически хранится в `core`, но endpoints находятся в
93+
модуле `users`:
94+
95+
- `GET /auth/users/specializations/nested/`;
96+
- `GET /auth/users/specializations/inline/`.
97+
98+
## Основные сценарии
99+
100+
### 1. Фронт получает справочник навыков
101+
102+
Для отображения навыков по категориям используется:
103+
104+
```text
105+
GET /core/skills/nested/
106+
```
107+
108+
Для поиска и autocomplete используется:
109+
110+
```text
111+
GET /core/skills/inline/?name__icontains=python
112+
```
113+
114+
### 2. Модуль привязывает навыки к объекту
115+
116+
Доменные модули создают `SkillToObject` через `ContentType`.
117+
118+
Например:
119+
120+
- `users` хранит навыки пользователя;
121+
- `vacancy` хранит требуемые навыки вакансии;
122+
- serializers используют `SkillToObjectSerializer` для единого response
123+
формата навыка.
124+
125+
### 3. Модуль фиксирует лайк или просмотр
126+
127+
`core.services` предоставляет функции:
128+
129+
- `set_like(obj, user, is_liked)`;
130+
- `add_like(obj, user)`;
131+
- `remove_like(obj, user)`;
132+
- `is_fan(obj, user)`;
133+
- `get_likes_count(obj)`;
134+
- `set_viewed(obj, user, is_viewed)`;
135+
- `add_view(obj, user)`;
136+
- `remove_view(obj, user)`;
137+
- `is_viewer(obj, user)`;
138+
- `get_views_count(obj)`.
139+
140+
Эти функции используются в `news`, `feed`, `partner_programs`,
141+
`project_rates` и других местах, где нужен generic-счетчик.
142+
143+
Важно: не все лайки в проекте уже переведены на generic-модель `core.Like`.
144+
Например, у проектов и мероприятий еще есть отдельные legacy-модели лайков.
145+
146+
### 4. Модуль формирует XLSX-выгрузку
147+
148+
Для выгрузок используются:
149+
150+
- `XlsxFileToExport`;
151+
- `sanitize_excel_value`;
152+
- `build_xlsx_download_response`.
153+
154+
Эти helpers применяются в `partner_programs`, `project_rates`, `courses`,
155+
`users` и `vacancy`.
156+
157+
### 5. WebSocket подключение проходит JWT-аутентификацию
158+
159+
`TokenAuthMiddleware` подключен в `procollab/asgi.py`.
160+
161+
Он ожидает WebSocket subprotocols в формате:
162+
163+
```text
164+
["Bearer", "<JWT>"]
165+
```
166+
167+
После проверки JWT middleware записывает пользователя в `scope["user"]`.
168+
Этим пользуется `chats.ChatConsumer`.
169+
170+
### 6. Чаты обновляют online-cache
171+
172+
`core.utils` содержит функции:
173+
174+
- `get_user_online_cache_key(user)`;
175+
- `get_users_online_cache_key()`.
176+
177+
`chats` пишет в эти ключи при подключении и отключении пользователя, а
178+
`metrics` читает aggregate-ключ для отображения количества пользователей онлайн.
179+
180+
## Связи с другими модулями
181+
182+
- `users` - навыки, специализации, online-флаги, Excel-выгрузки и permissions.
183+
- `vacancy` - required skills через `SkillToObject`, admin inline и выгрузки.
184+
- `projects` - общие serializers/permissions, счетчики просмотров, online
185+
данные пользователей.
186+
- `news` - generic likes/views.
187+
- `feed` - generic likes/views для записей ленты.
188+
- `partner_programs` - generic likes/views и Excel-выгрузки.
189+
- `project_rates` - счетчики просмотров проектов и выгрузки.
190+
- `courses` - Excel-выгрузка результатов.
191+
- `chats` - WebSocket auth и online-cache keys.
192+
- `metrics` - чтение online-cache.
193+
- `industries` и `events` - переиспользуют общие permissions.
194+
195+
## Ограничения и риски
196+
197+
- У `core` нет собственных тестов; shared-поведение проверяется в основном
198+
косвенно через другие модули.
199+
- `remove_link()` в `core.services` фильтрует `Like`, а не `Link`; это выглядит
200+
как баг.
201+
- `get_views_count()` кеширует значение, но `add_view()` / `remove_view()` не
202+
инвалидируют кеш.
203+
- `get_likes_count()` не использует кеш, хотя `LIKES_CACHING_TIMEOUT` объявлен.
204+
- `Skill`, `SkillCategory`, `Specialization` и `SpecializationCategory` не имеют
205+
уникальности по `name`.
206+
- `SkillToObject` и `SpecializationToObject` не ограничивают дубли на уровне
207+
модели.
208+
- `Base64ImageEncoder.get_encoded_base64_from_url()` использует `urlopen` без
209+
timeout.
210+
- `TokenAuthentication.authenticate()` не обрабатывает отсутствие пользователя
211+
после декодирования JWT.
212+
- `CustomLoguruMiddleware` пишет логи в директорию `log/` внутри `BASE_DIR`;
213+
окружение должно гарантировать доступность этой директории.
214+
- `CustomListField` преобразует список в строку через запятую и обратно; формат
215+
подходит не для всех типов значений.
216+
217+
## Тесты
218+
219+
Собственных тестов у модуля сейчас нет:
220+
221+
```text
222+
DEBUG=True .venv/bin/python manage.py test core
223+
```
224+
225+
Текущий запуск находит `0` тестов.
226+
227+
Поведение `core` частично покрывается тестами зависимых модулей:
228+
229+
- `news` и `feed` проверяют generic likes/views;
230+
- `vacancy` и `users` проверяют работу навыков;
231+
- `metrics` проверяет online-cache keys;
232+
- `partner_programs`, `project_rates`, `courses` проверяют Excel-выгрузки через
233+
общие helpers.

docs/modules/mailing.md

Lines changed: 137 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,139 @@
11
# Mailing
22

3-
TODO
3+
## Назначение
4+
5+
Модуль `mailing` отвечает за email-рассылки и шаблоны писем: хранение схем
6+
старой админской формы рассылки, подготовку данных письма, отправку сообщений
7+
через email backend и автоматические сценарии рассылок по партнерским
8+
программам.
9+
10+
## Статус модуля
11+
12+
Модуль используется в рабочих сценариях, но не подключен как публичный API.
13+
Основные активные точки использования:
14+
15+
- celery-задача `run_program_mailings`;
16+
- старая форма рассылки из админки партнерских программ;
17+
- общие helper-функции отправки писем, которые используют другие модули.
18+
19+
## Основные возможности
20+
21+
- хранение схемы письма в `MailingSchema`;
22+
- рендеринг старой админской формы рассылки;
23+
- подготовка данных письма из формы или typed dataclass;
24+
- массовая отправка писем по строковому шаблону;
25+
- массовая отправка писем по Django template;
26+
- группировка писем батчами;
27+
- сценарные рассылки участникам партнерских программ;
28+
- логирование результата сценарных рассылок в `MailingScenarioLog`.
29+
30+
## Архитектура
31+
32+
- `mailing/models.py` - модели схем писем и логов сценарных рассылок.
33+
- `mailing/utils.py` - подготовка данных письма и низкоуровневые функции
34+
отправки.
35+
- `mailing/scenarios.py` - декларативное описание сценариев рассылки по
36+
программам.
37+
- `mailing/tasks.py` - celery-задача запуска сценариев.
38+
- `mailing/rendering.py` - подстановка базовых placeholders в темы и тексты.
39+
- `mailing/views.py` - старые views для формы рассылки; сейчас не подключены в
40+
публичный URLConf.
41+
- `mailing/urls.py` - старые routes формы рассылки, не подключенные в
42+
`procollab/urls.py`.
43+
- `mailing/tests/` - regression-тесты моделей, rendering/helpers и сценариев.
44+
45+
## Ключевые сущности
46+
47+
- `MailingSchema` - схема шаблона письма и HTML-шаблон для старой формы
48+
рассылки.
49+
- `MailingScenarioLog` - лог отправки сценарного письма конкретному участнику
50+
программы за конкретную дату.
51+
- `Scenario` - dataclass с кодом сценария, триггером, правилом выбора
52+
получателей, шаблоном и builder-контекстом.
53+
- `EmailDataToPrepare` - typed input для подготовки данных письма из кода.
54+
55+
## API и внешние точки входа
56+
57+
Публичных endpoints модуля `mailing` сейчас нет: `mailing.urls` не подключен в
58+
корневой `procollab/urls.py`.
59+
60+
Связанные внешние точки:
61+
62+
- `/anymail/` - webhook routes библиотеки Anymail;
63+
- админка партнерской программы вызывает `MailingTemplateRender` напрямую через
64+
custom admin view;
65+
- celery beat запускает `mailing.tasks.run_program_mailings` каждый день в
66+
10:00.
67+
68+
## Основные сценарии
69+
70+
### 1. Сценарная рассылка по партнерским программам
71+
72+
`run_program_mailings()` проходит по сценариям из `SCENARIOS`.
73+
74+
Для каждого сценария:
75+
76+
- вычисляется целевая дата;
77+
- выбираются программы по дате регистрации, окончанию регистрации или дедлайну
78+
подачи проекта;
79+
- выбираются получатели по правилу сценария;
80+
- создаются `MailingScenarioLog` в статусе `pending`;
81+
- письмо отправляется через `send_mass_mail_from_template`;
82+
- статус лога меняется на `sent` или `failed` по `anymail_status`.
83+
84+
Повторная отправка за ту же дату не дублирует письма со статусом `pending` или
85+
`sent`.
86+
87+
### 2. Старая админская рассылка
88+
89+
`MailingTemplateRender` строит контекст формы:
90+
91+
- доступные `MailingSchema`;
92+
- выбранные и невыбранные пользователи;
93+
- поля шаблона из JSON-схемы.
94+
95+
Сейчас этот renderer используется из админки партнерских программ.
96+
97+
### 3. Отправка письма из других модулей
98+
99+
Другие модули могут подготовить `EmailDataToPrepare`, получить данные через
100+
`prepare_mail_data()` и отправить письмо через `send_mass_mail()`.
101+
102+
Такой flow сейчас использует `vacancy.tasks.send_email`, который также
103+
переиспользуется партнерскими программами и оценками проектов.
104+
105+
## Связи с другими модулями
106+
107+
- `partner_programs` - сценарные рассылки выбирают программы и участников через
108+
selectors; админка программ использует старый renderer формы рассылки.
109+
- `vacancy` - задачи вакансий используют mailing helpers для email-уведомлений.
110+
- `project_rates` - переиспользует общий notification flow через
111+
`vacancy.tasks.send_email`.
112+
- `users` - получатели писем.
113+
- `anymail` / Unisender Go - фактическая отправка писем в production.
114+
115+
## Ограничения и риски
116+
117+
- `mailing/urls.py` содержит старые routes, но они не подключены наружу.
118+
- Если старые routes будут снова подключены, для них нужно отдельно проверить
119+
permissions и безопасность массовой отправки.
120+
- В `mailing/urls.py` есть историческая опечатка `template_fileds`; менять ее
121+
без проверки старого UI не стоит.
122+
- `vacancy.tasks.send_email` фактически является общим helper для уведомлений,
123+
но находится в модуле вакансий.
124+
- `MailingScenarioLog` пока не зарегистрирован в Django admin.
125+
126+
## Тесты
127+
128+
Текущие regression-тесты проверяют:
129+
130+
- строковое представление `MailingSchema` и `MailingScenarioLog`;
131+
- подстановку placeholders в subject и template values;
132+
- контекст старого renderer формы рассылки;
133+
- подготовку данных письма из `EmailDataToPrepare`;
134+
- группировку писем батчами;
135+
- рендеринг и отправку писем по строковому шаблону;
136+
- отправку писем по Django template с `status_callback`;
137+
- выбор участников с неактивными аккаунтами для сценариев программ;
138+
- успешную сценарную рассылку без повторной отправки;
139+
- перевод сценарного лога в `failed` при ошибочном `anymail_status`.

0 commit comments

Comments
 (0)