P2-006 CRUD организаций и управление членами¶
Метаданные¶
| Поле | Значение |
|---|---|
| Фаза | Phase 2: Core |
| Статус | done |
| Приоритет | critical |
| Связь с roadmap | Roadmap - Организации |
Контекст¶
Бизнес-контекст¶
Организация — это tenant в системе AqStream. Все события, регистрации и данные принадлежат организации. Пользователь может быть членом нескольких организаций с разными ролями. Приглашение новых членов происходит через Telegram.
Технический контекст¶
- Organization.id используется как tenant_id во всех бизнес-таблицах
- Row Level Security изолирует данные организаций
- Роли: OWNER (один), MODERATOR (много)
- Приглашение через Telegram deeplink
Связанные документы: - User Service — API endpoints - Domain Model - Organization - Domain Model - OrganizationMember - Role Model - Data Architecture — RLS
Цель¶
Реализовать полный CRUD организаций, управление членами и переключение между организациями.
Definition of Ready (DoR)¶
- [x] Контекст понятен и описан
- [x] Цель сформулирована
- [x] Acceptance Criteria определены
- [x] Технические детали проработаны
- [x] Зависимости определены и разрешены
- [x] Нет блокеров
Acceptance Criteria¶
CRUD Организаций¶
- [x] Пользователь с одобренным запросом может создать организацию (
POST /api/v1/organizations) - [x] Slug из одобренного запроса используется автоматически
- [x] Создатель становится OWNER
- [x] Организация создаётся с настройками по умолчанию
- [x] OWNER может редактировать организацию (
PUT /api/v1/organizations/{id}) - [x] OWNER может удалить организацию (
DELETE /api/v1/organizations/{id}) — soft delete - [x] При удалении все события архивируются (event-service OrganizationEventListener слушает
organization.deleted)
Управление членами¶
- [x] Список членов организации (
GET /api/v1/organizations/{id}/members) - [x] Приглашение нового члена через Telegram (
POST /api/v1/organizations/{id}/invite) - [x] Приглашённый получает deeplink в Telegram
- [x] При переходе по ссылке — становится MODERATOR
- [x] Изменение роли члена (
PUT /api/v1/organizations/{id}/members/{userId}) - [x] Удаление члена (
DELETE /api/v1/organizations/{id}/members/{userId}) - [x] OWNER не может быть удалён
- [x] Приглашение действительно 7 дней (FR-3.2.5)
Роли и права¶
- [x] OWNER — полный контроль, удаление организации, передача владения
- [x] MODERATOR — управление событиями, членами (кроме OWNER), check-in
- [x] Только OWNER может назначать роли
- [x] Только OWNER может удалять организацию
Переключение организаций¶
- [x] Пользователь видит список своих организаций (
GET /api/v1/organizations) - [x] Пользователь может переключиться на другую организацию
- [x] При переключении выдаётся новый access token с другим tenantId
Definition of Done (DoD)¶
- [x] Все Acceptance Criteria выполнены (frontend — отдельная задача P2-016)
- [x] Код написан согласно code style проекта
- [x] Unit тесты написаны
- [x] Integration тесты написаны (OrganizationControllerIntegrationTest добавлен)
- [x] Миграции созданы (RLS политики — P2-007)
- [x] Events опубликованы в RabbitMQ (organization.created, organization.updated, organization.deleted, organization.member.*)
- [x] Code review пройден
- [x] CI/CD pipeline проходит
Технические детали¶
Затрагиваемые компоненты¶
- [x] Backend:
user-service-api,user-service-service,user-service-db - [ ] Frontend: создание организации, управление членами, переключатель → P2-016
- [x] Database: таблицы
organizations,organization_members,organization_invites - [x] Infrastructure: —
Модель данных¶
-- Organizations
CREATE TABLE user_service.organizations (
id UUID PRIMARY KEY,
owner_id UUID NOT NULL REFERENCES user_service.users(id),
name VARCHAR(255) NOT NULL,
slug VARCHAR(50) NOT NULL UNIQUE,
description TEXT,
logo_url VARCHAR(500),
settings JSONB DEFAULT '{}',
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
deleted_at TIMESTAMP
);
-- Organization Members
CREATE TABLE user_service.organization_members (
id UUID PRIMARY KEY,
organization_id UUID NOT NULL REFERENCES user_service.organizations(id),
user_id UUID NOT NULL REFERENCES user_service.users(id),
role VARCHAR(20) NOT NULL, -- OWNER, MODERATOR
invited_by UUID REFERENCES user_service.users(id),
joined_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
UNIQUE(organization_id, user_id)
);
-- Organization Invites
CREATE TABLE user_service.organization_invites (
id UUID PRIMARY KEY,
organization_id UUID NOT NULL REFERENCES user_service.organizations(id),
invite_code VARCHAR(32) NOT NULL UNIQUE,
invited_by UUID NOT NULL REFERENCES user_service.users(id),
telegram_username VARCHAR(100),
role VARCHAR(20) NOT NULL DEFAULT 'MODERATOR',
expires_at TIMESTAMP NOT NULL,
used_at TIMESTAMP,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
API Endpoints¶
# Organizations
GET /api/v1/organizations — список организаций пользователя
POST /api/v1/organizations — создание организации
GET /api/v1/organizations/{id} — детали организации
PUT /api/v1/organizations/{id} — обновление
DELETE /api/v1/organizations/{id} — удаление (soft delete)
POST /api/v1/organizations/{id}/switch — переключение (новый token)
# Members
GET /api/v1/organizations/{id}/members — список членов
POST /api/v1/organizations/{id}/invite — создание приглашения
PUT /api/v1/organizations/{id}/members/{userId} — изменение роли
DELETE /api/v1/organizations/{id}/members/{userId} — удаление члена
# Invite acceptance
POST /api/v1/organizations/join/{inviteCode} — принятие приглашения
RabbitMQ Events¶
organization.createdorganization.updatedorganization.deletedorganization.member.addedorganization.member.removedorganization.member.role.changed
Зависимости¶
Блокирует¶
Зависит от¶
Out of Scope¶
- Billing и подписки организаций
- Custom domain для организации
- Branding (кастомные цвета, логотипы на страницах)
- Передача владения (можно добавить позже)
Заметки¶
- При создании организации нужно также создать первую запись в organization_members с role=OWNER
- Telegram deeplink формат:
https://t.me/AqStreamBot?start=invite_{code} - При переключении организации нужно генерировать новый access token