meraproject/openspec/changes/archive/2026-07-14-service-project-members/design.md
keboss-m a7f3df859a Add service API for assigning employees to project teams.
Expose PUT /api/service/project-members for external apps that authenticate with only the API key, reusing portal validation while keeping acting-based writes unchanged.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-14 15:30:59 +03:00

81 lines
6.1 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

## Context
User-reader уже умеет upsert участника проекта через `PUT /api/project-members` (`project_members_write.py`): валидация раздела/роли/сотрудника и запись в `tMerakomisTeamMember`. Авторизация завязана на `X-Acting-Emp-Id` и `can_write_project_member` (паритет PHP `Rules::isRwMember`).
Внешнее приложение знает `project_id`, `emp_id`, `section_id`, `role` и имеет только service API-ключ. Impersonation РП/ГИП для него неудобна и нежелательна.
Справочник разделов проекта (`GET /api/project-sections`) уже отдаёт `section_id`; отдельного поля «раздел + подраздел» в модели нет — в TeamMember хранится один `section`.
## Goals / Non-Goals
**Goals:**
- Дать внешним сервисам upsert участника проекта только по API-ключу.
- Переиспользовать существующую бизнес-валидацию и upsert-логику без дублирования SQL.
- Явно отделить service-контракт от портального acting-потока (отдельный URL).
- Зафиксировать контракт в docs и тестах.
**Non-Goals:**
- Создание/редактирование сотрудника в `tMerakomisEmp`.
- Изменение поведения `PUT /api/project-members` (acting остаётся обязательным).
- Расширение `GET /api/project-sections` (parent / full_name) — не требуется для этого сценария.
- Отдельный API-ключ или scopes на эндпоинт (пока один `USER_READER_API_KEY`).
- Запись разделов проекта (`tMerakomisProjectSection`) или создание команды проекта.
## Decisions
### 1. Отдельный путь `PUT /api/service/project-members` (вариант B)
**Выбор:** новый URL под префиксом `/api/service/…`, а не опциональный acting на существующем PUT.
**Почему:**
- Контракт для интеграций явный: «сервис доверяет ключу», не «человек с правами».
- Не ослабляем портальный эндпоинт случайным вызовом без acting.
- Проще документировать и аудировать.
**Альтернатива (A):** тот же PUT, acting опционален → отвергнута: смешивает модели доверия.
### 2. Переиспользование `ProjectMemberWrite` + `validate_member_write` + `upsert_team_member`
**Выбор:** тот же JSON-контракт и те же helper-функции; новый handler только пропускает acting/permissions.
**Почему:** один источник правил (раздел ∈ проекта, роль валидна, emp не архивный, у проекта есть team).
**Реализация:** тонкий роут в новом модуле (например `project_members_service.py`) или второй handler в `project_members_write.py` с `Depends(require_api_key)` без `parse_acting_emp_id`. Предпочтительно отдельный файл/роутер, чтобы граница service vs portal была видна в `main.py`.
### 3. Авторизация = только API-ключ
**Выбор:** при включённом `USER_READER_API_KEY` — обязателен ключ; при пустом ключе (локальная отладка) — как у остальных защищённых эндпоинтов. Проверки `isRwMember` нет.
**Почему:** соответствует заявленному сценарию внешнего приложения. Риск компенсируется контролем ключа на стороне деплоя.
### 4. Ответ и ошибки
**Выбор:** тот же shape, что у portal PUT: `ok: true` + поля item `GET /api/project-members`. Те же коды валидации (`400`/`404`), без `403 forbidden` из-за прав acting (acting нет). `401` при неверном/отсутствующем ключе.
### 5. Документация
Обновить матрицу доступа и раздел записи в `docs/user-reader-api.md` (+ краткая строка в `DEVELOPERS.md`). В сценариях интеграции указать: внешний сервис → service PUT; UI/табель от имени сотрудника → portal PUT.
## Risks / Trade-offs
| Risk | Mitigation |
|------|------------|
| Утечка API-ключа = полный write состава любых проектов | Ключ только на сервере; ротация; в будущем — scopes / отдельный ключ для write |
| Клиенты путают portal и service PUT | Разные URL + явная секция в docs; portal по-прежнему требует Acting |
| Дублирование логики при копировании кода | Обязательно вызывать существующие validate/upsert, не копировать SQL |
| Нет audit «кто добавил» (нет acting) | Принять для v1; при необходимости позже писать в History с маркером `service` |
## Migration Plan
1. Задеплоить user-reader с новым роутом (обратно совместимо).
2. Внешнее приложение переключить на `PUT /api/service/project-members`.
3. Rollback: убрать роут / откатить образ — portal PUT не затронут.
## Open Questions
- Нужен ли позже audit/history для service-записей? (вне scope v1)
- Нужен ли отдельный write-ключ или scope? (вне scope v1; один общий ключ)