|
1 | 1 | # Invites |
2 | 2 |
|
3 | | -TODO |
| 3 | +## Назначение |
| 4 | + |
| 5 | +Invites отвечает за приглашения пользователей в команду проекта. |
| 6 | + |
| 7 | +Модуль закрывает сценарий, когда лидер проекта приглашает пользователя на роль в |
| 8 | +проекте, а пользователь принимает или отклоняет приглашение. |
| 9 | + |
| 10 | +## Статус модуля |
| 11 | + |
| 12 | +Модуль рабочий и подключен в публичный API через `/invites/`. |
| 13 | + |
| 14 | +Приглашения доступны приглашенному пользователю, лидеру проекта и |
| 15 | +staff/superuser. Изменять или удалять приглашение может лидер проекта. Принять |
| 16 | +или отклонить приглашение может приглашенный пользователь. |
| 17 | + |
| 18 | +## Основные возможности |
| 19 | + |
| 20 | +- создание приглашения лидером проекта; |
| 21 | +- просмотр списка активных приглашений; |
| 22 | +- фильтрация приглашений по проекту и пользователю; |
| 23 | +- просмотр, обновление и удаление приглашения; |
| 24 | +- принятие приглашения пользователем; |
| 25 | +- отклонение приглашения пользователем; |
| 26 | +- автоматическое добавление пользователя в collaborators проекта после принятия; |
| 27 | +- запрет приглашения лидера проекта; |
| 28 | +- запрет приглашения пользователя, который уже состоит в проекте; |
| 29 | +- запрет повторного активного приглашения в тот же проект; |
| 30 | +- проверка участия пользователя в партнерской программе, если проект привязан к |
| 31 | + программе. |
| 32 | + |
| 33 | +## Архитектура |
| 34 | + |
| 35 | +- `invites/models.py` - модель `Invite`. |
| 36 | +- `invites/serializers.py` - serializers создания и чтения приглашений. |
| 37 | +- `invites/views.py` - API endpoints и основная orchestration logic. |
| 38 | +- `invites/filters.py` - фильтры списка приглашений. |
| 39 | +- `invites/managers.py` - queryset helper для списка. |
| 40 | +- `invites/querysets.py` - queryset видимых приглашений для текущего |
| 41 | + пользователя. |
| 42 | +- `invites/permissions.py` - object-level permissions для detail и |
| 43 | + accept/decline. |
| 44 | +- `invites/urls.py` - routes модуля. |
| 45 | +- `invites/admin.py` - регистрация приглашений в Django admin. |
| 46 | +- `invites/tests/` - regression-тесты и helpers модуля. |
| 47 | + |
| 48 | +## Основные сущности |
| 49 | + |
| 50 | +### Invite |
| 51 | + |
| 52 | +`Invite` хранит приглашение пользователя в проект. |
| 53 | + |
| 54 | +Поля: |
| 55 | + |
| 56 | +- `project` - проект, в который приглашают пользователя; |
| 57 | +- `user` - приглашенный пользователь; |
| 58 | +- `motivational_letter` - текст приглашения; |
| 59 | +- `role` - роль пользователя в проекте после принятия; |
| 60 | +- `specialization` - специализация пользователя после принятия; |
| 61 | +- `is_accepted` - статус приглашения: |
| 62 | + - `None` - ожидает решения; |
| 63 | + - `True` - принято; |
| 64 | + - `False` - отклонено; |
| 65 | +- `datetime_created` - дата создания; |
| 66 | +- `datetime_updated` - дата обновления. |
| 67 | + |
| 68 | +Сортировка по умолчанию: новые приглашения выше. |
| 69 | + |
| 70 | +## API |
| 71 | + |
| 72 | +- `GET /invites/` - список активных приглашений. |
| 73 | +- `POST /invites/` - создание приглашения. |
| 74 | +- `GET /invites/<id>/` - детали приглашения. |
| 75 | +- `PUT /invites/<id>/` - полное обновление приглашения. |
| 76 | +- `PATCH /invites/<id>/` - частичное обновление приглашения. |
| 77 | +- `DELETE /invites/<id>/` - удаление приглашения. |
| 78 | +- `POST /invites/<id>/accept/` - принять приглашение. |
| 79 | +- `POST /invites/<id>/decline/` - отклонить приглашение. |
| 80 | + |
| 81 | +Фильтры списка: |
| 82 | + |
| 83 | +- `project` - фильтр по проекту; |
| 84 | +- `user` - фильтр по пользователю; |
| 85 | +- `user=any` - staff-only фильтр для отключения пользовательского фильтра. |
| 86 | + |
| 87 | +Список всегда ограничен активными приглашениями: `is_accepted IS NULL`. |
| 88 | + |
| 89 | +## Доступ и права |
| 90 | + |
| 91 | +- список приглашений доступен только authenticated пользователю; |
| 92 | +- приглашенный пользователь видит свои приглашения; |
| 93 | +- лидер проекта видит приглашения своего проекта через `project=<id>`; |
| 94 | +- staff/superuser может получить все активные приглашения через `user=any`; |
| 95 | +- `user=<id>` работает только внутри уже разрешенной области видимости; |
| 96 | +- `user=any` запрещен для обычных пользователей и лидеров проекта; |
| 97 | +- detail приглашения видят только приглашенный пользователь, лидер проекта и |
| 98 | + staff/superuser; |
| 99 | +- обновить или удалить приглашение может только лидер проекта; |
| 100 | +- принять или отклонить приглашение может только приглашенный пользователь; |
| 101 | +- повторный `accept` или `decline` по уже обработанному приглашению возвращает |
| 102 | + `409 Conflict` и не меняет финальный статус. |
| 103 | + |
| 104 | +## Основные сценарии |
| 105 | + |
| 106 | +### 1. Лидер проекта создает приглашение |
| 107 | + |
| 108 | +Лидер проекта отправляет `POST /invites/` с проектом, пользователем, ролью, |
| 109 | +специализацией и текстом приглашения. |
| 110 | + |
| 111 | +При создании: |
| 112 | + |
| 113 | +- serializer проверяет, что пользователь не является лидером проекта; |
| 114 | +- serializer проверяет, что пользователь еще не collaborator проекта; |
| 115 | +- serializer запрещает повторное активное приглашение в тот же проект; |
| 116 | +- если проект привязан к партнерской программе, пользователь должен быть |
| 117 | + участником этой программы; |
| 118 | +- view отдельно проверяет, что текущий пользователь является лидером проекта. |
| 119 | + |
| 120 | +### 2. Пользователь видит свои приглашения |
| 121 | + |
| 122 | +Пользователь получает список через `GET /invites/`. |
| 123 | + |
| 124 | +По умолчанию фильтр `user` подставляется из `request.user`, поэтому |
| 125 | +приглашенный пользователь видит свои активные приглашения. Лидер проекта |
| 126 | +получает активные приглашения проекта через `GET /invites/?project=<id>`. Staff |
| 127 | +или superuser может работать со всем списком через `GET /invites/?user=any`. |
| 128 | + |
| 129 | +### 3. Пользователь принимает приглашение |
| 130 | + |
| 131 | +Пользователь вызывает `POST /invites/<id>/accept/`. |
| 132 | + |
| 133 | +При успешном принятии: |
| 134 | + |
| 135 | +- проверяется, что приглашение принимает именно приглашенный пользователь; |
| 136 | +- если приглашение уже принято или отклонено, возвращается conflict; |
| 137 | +- пользователь добавляется в `Collaborator` проекта; |
| 138 | +- в collaborator переносятся `role` и `specialization` из приглашения; |
| 139 | +- приглашение получает `is_accepted=True`. |
| 140 | + |
| 141 | +### 4. Пользователь отклоняет приглашение |
| 142 | + |
| 143 | +Пользователь вызывает `POST /invites/<id>/decline/`. |
| 144 | + |
| 145 | +При успешном отклонении: |
| 146 | + |
| 147 | +- проверяется, что приглашение отклоняет именно приглашенный пользователь; |
| 148 | +- если приглашение уже принято или отклонено, возвращается conflict; |
| 149 | +- приглашение получает `is_accepted=False`. |
| 150 | + |
| 151 | +### 5. Приглашение влияет на доступ к проекту |
| 152 | + |
| 153 | +В `projects.permissions` приглашенный пользователь считается вовлеченным в |
| 154 | +проект. Это дает доступ к непубличному или draft-проекту до принятия |
| 155 | +приглашения. |
| 156 | + |
| 157 | +## Связи с другими модулями |
| 158 | + |
| 159 | +- `projects` - приглашение всегда связано с проектом; после принятия создается |
| 160 | + `Collaborator`. |
| 161 | +- `users` - приглашение связано с приглашенным пользователем и лидером проекта. |
| 162 | +- `partner_programs` - если проект связан с партнерской программой, приглашать |
| 163 | + можно только участника этой программы. |
| 164 | +- `projects.permissions` - invite участвует в проверках доступа к непубличным и |
| 165 | + draft-проектам. |
| 166 | + |
| 167 | +## Ограничения и риски |
| 168 | + |
| 169 | +- Проверка участия в партнерской программе смотрит только первую связь |
| 170 | + `project.program_links.first()`. Если проект может быть связан с несколькими |
| 171 | + программами, этот контракт нужно уточнить. |
| 172 | +- Нет DB-level constraint для запрета нескольких активных приглашений одного |
| 173 | + пользователя в один проект. Сейчас это защищено только serializer validation. |
| 174 | +- Вся orchestration logic находится во views/serializers, отдельного service |
| 175 | + layer пока нет. |
| 176 | + |
| 177 | +## Тесты |
| 178 | + |
| 179 | +Текущие тесты лежат в `invites/tests/`. |
| 180 | + |
| 181 | +Проверяется: |
| 182 | + |
| 183 | +- создание приглашения лидером проекта; |
| 184 | +- создание приглашения без motivational letter; |
| 185 | +- запрет создания приглашения пользователем, который не является лидером |
| 186 | + проекта; |
| 187 | +- ошибка при пустом payload; |
| 188 | +- запрет приглашения лидера проекта; |
| 189 | +- запрет приглашения существующего collaborator; |
| 190 | +- запрет повторного активного приглашения в тот же проект; |
| 191 | +- запрет приглашения пользователя, который не является участником программы |
| 192 | + проекта; |
| 193 | +- создание приглашения для участника программы, если проект привязан к |
| 194 | + программе; |
| 195 | +- список активных приглашений текущего пользователя; |
| 196 | +- фильтр списка по проекту: лидер проекта видит все активные приглашения этого |
| 197 | + проекта; |
| 198 | +- фильтр списка по пользователю внутри разрешенной области видимости; |
| 199 | +- `user=any` доступен только staff/superuser; |
| 200 | +- запрет anonymous list; |
| 201 | +- права чтения detail: приглашенный пользователь, лидер проекта и staff имеют |
| 202 | + доступ, outsider и anonymous не имеют; |
| 203 | +- права update/delete: только лидер проекта может менять или удалять |
| 204 | + приглашение; |
| 205 | +- принятие приглашения приглашенным пользователем с созданием collaborator и |
| 206 | + переносом `role` / `specialization`; |
| 207 | +- отклонение приглашения приглашенным пользователем; |
| 208 | +- запрет accept/decline чужим пользователем; |
| 209 | +- запрет accept/decline лидером проекта вместо приглашенного пользователя; |
| 210 | +- conflict при повторном accept уже обработанного приглашения; |
| 211 | +- conflict при повторном decline уже обработанного приглашения; |
| 212 | +- защита decline после accept: статус остается принятым, collaborator остается |
| 213 | + в проекте; |
| 214 | +- строковое представление `Invite`. |
| 215 | + |
| 216 | +Сейчас не покрыты: |
| 217 | + |
| 218 | +- DB-level constraint для повторных активных приглашений; |
| 219 | +- сценарий проекта, связанного с несколькими партнерскими программами. |
0 commit comments