|
1 | 1 | # Core |
2 | 2 |
|
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. |
0 commit comments