Admin API — админ-панель
Слой:
libs/apis/providers/admin-api· Приложение:apps/admin-api
Назначение
Заголовок раздела «Назначение»Admin API — отдельное NestJS-приложение (backend бэк-офиса CrewsForge). Оно обслуживает три рабочих места платформы — оператора, администратора и арбитра — и публичные формы лендинга.
Администраторская поверхность:
- просмотр и поиск пользователей, блокировка/разблокировка;
- просмотр и поиск проектов, редактирование, мягкое удаление и восстановление;
- просмотр команд-исполнителей с их составом и верификация команды, проходящей онбординг.
Рабочие места оператора и арбитра пока представлены только точкой входа /{роль}/me: она отвечает, кто вошёл и в какую поверхность его пустили.
Публичные формы лендинга (/contact/*) живут в том же приложении и авторизации не требуют.
Приложение поднимается на своём порту, имеет собственную пару JWT-секретов (jwt-admin) и собственный набор фич в слое libs/apis/providers/admin-api. Бизнес-логика вынесена в фичи-библиотеки, а само приложение (apps/admin-api) выполняет только bootstrap.
Технические параметры bootstrap (apps/admin-api/src/main.ts):
- порт:
process.env.ADMIN_API_PORTили3002по умолчанию; - глобальный префикс:
api/v1, версионирование через URI (VersioningType.URI); - Swagger:
api/v1/docs, заголовок «Admin API», bearer-схемаjwt-admin-access; - глобальный
ValidationPipe(whitelist,forbidNonWhitelisted,transform); - глобальный
PrismaExceptionFilter,cookie-parser, CORS сcredentials: true; - транзакции через
nestjs-cls+ClsPluginTransactional(адаптер Prisma).
AppController / AppService содержат только заглушку GET / → { message: 'Hello API' } и не относятся к админ-функциональности.
Авторизация админа
Заголовок раздела «Авторизация админа»Авторизация двухслойная: AdminAccessTokenGuard отвечает на вопрос «кто это», платформенный guard — «в какую поверхность его пускают». Оба ставятся на уровне класса контроллера: @UseGuards(AdminAccessTokenGuard, AdministratorGuard) и так далее.
AdminAccessTokenGuard (libs/apis/shared/.../guards/admin-access-token.guard.ts) — passport-AuthGuard поверх стратегии ADMIN_ACCESS_TOKEN_STRATEGY_NAME = 'jwt-admin-access'.
Стратегия AdminAccessTokenStrategy (auth-api/features/admin-auth) при каждом запросе:
- извлекает Bearer-токен из заголовка
Authorization; - проверяет подпись секретом
jwt-admin.accessSecretKey; - проверяет, что access-токен зарегистрирован (
tokenService.findOneByAccessTokenId); - проверяет, что пользователь существует и не заблокирован (
isBlocked); - кладёт
TokenDecodePayload(в т.ч.userId) вrequest.user.
Платформенная роль и claim platformRole
Заголовок раздела «Платформенная роль и claim platformRole»В admin-api пускают три платформенные роли — ADMIN, OPERATOR, ARBITER — и ровно одну на человека. Правило проходит через всю цепочку:
- Логин (
AdminAuthService.login) требует e-mail в разрешённом домене (ADMIN_ALLOWED_DOMAINS), незаблокированный аккаунт и ровно одну платформенную роль. Отсутствие роли и совмещение двух —403 Forbidden, в лог уходитwarnбез секретов. - Выдача токена (
AdminAuthVerificationService) кладёт в payload claimplatformRoleсо значениемADMIN | OPERATOR | ARBITER. Значение выводится изUserOnRole, а не подставляется литералом: содержимое токена и содержимое базы расходиться не должны. - Refresh (
AdminAuthRefreshTokenService) перевыводит иroles, иplatformRoleна каждой ротации — иначе claim исчезал бы после первого обновления, а снятая роль продолжала бы жить в токене до истечения. - Guard рабочего места (
OperatorGuard/AdministratorGuard/ArbiterGuard) сверяет claim с требуемой ролью и отдаёт403при несовпадении. - База отвергает вторую платформенную роль частичным уникальным индексом
users_on_roles_single_platform_role— см. database-schema.
Вывод роли живёт в одной функции resolvePlatformRole / resolvePlatformRoleOrFail (auth-api/features/token), а не размазан по точкам выпуска токена.
Совмещение ролей запрещено не из аккуратности: оператор «по совместительству администратор» видел бы деньги, и разделение обязанностей (оператор готовит решение — администратор его утверждает) перестало бы что-либо значить.
Регистрация в admin-api (POST /auth/admin/register) заводит только роль ADMIN. Оператор и арбитр создаются сидом или назначением роли существующему пользователю (см. seeds).
RoleGuard из shared и enum Role работают только с CUSTOMER/EMPLOYEE и в admin-api не используются.
Чем отличается от customer / employee:
- Отдельная пара секретов. Токены подписываются
JWT_ADMIN_*-секретами, а не customer/employee-секретами — customer- или employee-токен не пройдётAdminAccessTokenGuard. - Отдельные гварды/стратегии.
AdminAccessTokenGuard(jwt-admin-access) противCustomerAccessTokenGuard/EmployeeAccessTokenGuard. - Ограничение по домену. Для админов действует allow-list доменов e-mail (
ADMIN_ALLOWED_DOMAINS), которого нет у обычных пользователей. - Платформенные guard’ы. Разграничение внутри бэк-офиса даёт claim
platformRole, которого нет ни у customer-, ни у employee-токена.
Переменные окружения jwt-admin (libs/apis/configs/auth-api/jwt-admin):
| Переменная | Назначение | По умолчанию / валидация |
|---|---|---|
JWT_ADMIN_ACCESS_SECRET_KEY | секрет подписи admin access-токена | required (Joi) |
JWT_ADMIN_ACCESS_EXPIRES_IN | TTL access-токена, сек. | 3600, если не число |
JWT_ADMIN_REFRESH_SECRET_KEY | секрет подписи admin refresh-токена | required (Joi) |
JWT_ADMIN_REFRESH_EXPIRES_IN | TTL refresh-токена, сек. | 7200, если не число |
ADMIN_ALLOWED_DOMAINS | список разрешённых доменов e-mail админов (через запятую) | required (Joi); парсится в массив в нижнем регистре |
Конфиг регистрируется через registerAs('jwt-admin', ...), доступ — через JwtAdminConfigService.
Состав (фичи)
Заголовок раздела «Состав (фичи)»| Фича (библиотека) | Контроллер / базовый путь | Назначение |
|---|---|---|
admin-api/features/workspace | OperatorWorkspaceController → operator, AdministratorWorkspaceController → administrator, ArbiterWorkspaceController → arbiter | Точки входа трёх рабочих мест: GET /{роль}/me |
admin-api/features/admin-user | AdminUserController → users | Список/поиск пользователей, карточка пользователя, профиль текущего админа, блокировка/разблокировка |
admin-api/features/admin-project | AdminProjectController → projects | Список/поиск проектов, карточка, редактирование, мягкое удаление/восстановление |
admin-api/features/admin-team | AdminTeamController → administrator/teams | Список команд, карточка с составом, верификация онбординга |
admin-api/features/admin-contact | AdminContactController → contact | Публичные формы лендинга: заявка и обращение в поддержку |
admin-api/data-access | — | Общие константы (пагинация, префикс) и query/командные DTO |
Каждая фича — модуль NestJS с тройкой Controller → Service → Repository (Prisma). Исключение —
admin-team: своего репозитория у неё нет, она работает поверх TeamCoreModule из
user-api и добавляет только форму ответа бэк-офиса.
HTTP API
Заголовок раздела «HTTP API»Все пути ниже даны без глобального префикса. Полный URL = /api/v1/<path>. Всё, кроме /contact/*, защищено парой guard’ов: AdminAccessTokenGuard (Bearer jwt-admin-access) + guard рабочего места.
workspace (@Controller('operator' | 'administrator' | 'arbiter'))
Заголовок раздела «workspace (@Controller('operator' | 'administrator' | 'arbiter'))»| Метод | Путь | Guard | Описание | DTO |
|---|---|---|---|---|
GET | /operator/me | AdminAccessTokenGuard, OperatorGuard | Кто вошёл в рабочее место оператора | WorkspaceMeResponseDto |
GET | /administrator/me | AdminAccessTokenGuard, AdministratorGuard | То же для администратора | WorkspaceMeResponseDto |
GET | /arbiter/me | AdminAccessTokenGuard, ArbiterGuard | То же для арбитра | WorkspaceMeResponseDto |
WorkspaceMeResponseDto — три поля: userId, email, platformRole. Больше здесь быть не должно: рабочее место спрашивает «кто я и куда меня пустили», а не карточку пользователя.
Роль в ответе берётся из базы, а не из claim’а: токен уже проверен guard’ом, и второй источник истины только развёл бы ответ с реальными правами. Отсутствие платформенной роли и совмещение двух — 403.
Чужое рабочее место закрыто в обе стороны: оператор на /administrator/me и /arbiter/me получает 403, администратор на /operator/me — тоже, и так для всех трёх ролей.
admin-contact (@Controller('contact'))
Заголовок раздела «admin-contact (@Controller('contact'))»| Метод | Путь | Guard | Описание | DTO |
|---|---|---|---|---|
POST | /contact/lead | — | Заявка с лендинга, 204 No Content | LeaveRequestDto |
POST | /contact/support | — | Обращение в поддержку с лендинга, 204 No Content | SupportRequestDto |
Эндпоинты публичные намеренно: их вызывает лендинг, у посетителя которого нет и не может быть токена. Детальный контракт — в README библиотеки.
Оператор и арбитр на /users, /projects и /administrator/teams получают 403: это администраторская поверхность.
admin-user (@Controller('users'))
Заголовок раздела «admin-user (@Controller('users'))»| Метод | Путь | Guard | Описание | DTO |
|---|---|---|---|---|
GET | /users | AdminAccessTokenGuard, AdministratorGuard | Список пользователей с пагинацией и фильтрами | Query: AdminUserQueryDto → отдаёт { data, total, page, perPage } (AdminUserResponseDto) |
GET | /users/me | AdminAccessTokenGuard, AdministratorGuard | Профиль текущего админа (по userId из токена) | AdminMeResponseDto |
GET | /users/:id | AdminAccessTokenGuard, AdministratorGuard | Карточка пользователя по UUID (с профилем, ролями, проектами) | id: ParseUUIDPipe; AdminUserDetailResponseDto |
PATCH | /users/:id/block | AdminAccessTokenGuard, AdministratorGuard | Заблокировать пользователя | id: ParseUUIDPipe; тела нет |
PATCH | /users/:id/unblock | AdminAccessTokenGuard, AdministratorGuard | Разблокировать пользователя | id: ParseUUIDPipe; тела нет |
Фильтры/пагинация AdminUserQueryDto:
page(int ≥ 1, по умолчанию1);perPage(int 1..100, по умолчаниюDEFAULT_PAGE_SIZE = 10);role(enumRoleType) — фильтр по роли (userOnRole.some.role.type);isBlocked(boolean, из строки'true') — фильтр по блокировке;search(строка ≤ 255,@Trim) — поиск поemail, регистронезависимо.
admin-project (@Controller('projects'))
Заголовок раздела «admin-project (@Controller('projects'))»| Метод | Путь | Guard | Описание | DTO |
|---|---|---|---|---|
GET | /projects | AdminAccessTokenGuard, AdministratorGuard | Список проектов с пагинацией и фильтрами (только не удалённые) | Query: AdminProjectQueryDto → { data, total, page, perPage } (AdminProjectResponseDto) |
GET | /projects/:id | AdminAccessTokenGuard, AdministratorGuard | Карточка проекта по UUID (только не удалённый) | id: ParseUUIDPipe; AdminProjectResponseDto |
PATCH | /projects/:id | AdminAccessTokenGuard, AdministratorGuard | Редактирование проекта | id: ParseUUIDPipe; Body: AdminProjectUpdateDto |
DELETE | /projects/:id | AdminAccessTokenGuard, AdministratorGuard | Мягкое удаление (204 No Content) | id: ParseUUIDPipe; adminId из токена |
PATCH | /projects/:id/restore | AdminAccessTokenGuard, AdministratorGuard | Восстановление мягко удалённого проекта | id: ParseUUIDPipe |
Фильтры/пагинация AdminProjectQueryDto:
page(int ≥ 1, по умолчанию1);perPage(int 1..100, по умолчанию10);status(enumProjectStatus);category(enumProjectCategoryType);search(строка ≤ 255,@Trim) — поиск поtitleиdescription, регистронезависимо.
AdminProjectUpdateDto (тело PATCH /projects/:id): title? (3..255), description? (≥ 3), category? (ProjectCategoryType) — все поля опциональны. Поля status в теле нет.
admin-team (@Controller('administrator/teams'))
Заголовок раздела «admin-team (@Controller('administrator/teams'))»| Метод | Путь | Guard | Описание | DTO |
|---|---|---|---|---|
GET | /administrator/teams | AdminAccessTokenGuard, AdministratorGuard | Список команд с пагинацией, новые сверху | Query: AdminTeamQueryDto → { data, meta } (AdminTeamResponseDto) |
GET | /administrator/teams/:teamId | AdminAccessTokenGuard, AdministratorGuard | Карточка команды вместе с составом | teamId: ParseUUIDPipe; AdminTeamDetailResponseDto |
POST | /administrator/teams/:teamId/verify | AdminAccessTokenGuard, AdministratorGuard | Верифицировать команду, проходящую онбординг (201) | teamId: ParseUUIDPipe; тела нет; AdminTeamResponseDto |
Базовый путь контроллера начинается с administrator/ — маршруты скоупятся рабочим местом, как и
остальные его поверхности; оператор и арбитр получают здесь 403.
Фильтры/пагинация AdminTeamQueryDto:
page(int ≥ 1);perPage(int 1..MAX_PAGE_SIZE);status(enumTeamStatus).
Значения по умолчанию (page = 1, perPage = 10) ставит домен в TeamService, второго набора
дефолтов в admin-api нет. Ответ списка — общий пагинационный конверт { data, meta }
(MapperService.toPaginateResponse), а не { data, total, page, perPage }, как у users
и projects.
AdminTeamResponseDto: id, createdAt, updatedAt, name, slug, bio, status,
externalExperience, verifiedAt. AdminTeamDetailResponseDto добавляет members —
AdminTeamMemberResponseDto с employeeId, role, canSign, canSubmit, status, removedAt.
Карточки сотрудников в составе нет: их бэк-офис берёт в своей поверхности по employeeId.
Ключевые операции
Заголовок раздела «Ключевые операции»Блокировка пользователя (PATCH /users/:id/block, .../unblock)
Заголовок раздела «Блокировка пользователя (PATCH /users/:id/block, .../unblock)»AdminUserService.blockUser(id, adminId):
- загружает пользователя вместе с ролями (
findOneById(id, true)); если нет —404 NotFound; - если у пользователя есть роль
ADMIN— операция запрещена (403 Forbidden, «Cannot block a user with ADMIN role»), в лог пишетсяadmin.user.block_admin_rejected; - если пользователь уже заблокирован —
400 BadRequest; - иначе
update(id, { isBlocked: true }), логadmin.user.blocked, ответ{ message: 'User has been blocked' }.
unblockUser(id) — зеркально: 404, если пользователя нет; 400, если пользователь не заблокирован; иначе isBlocked: false и { message: 'User has been unblocked' }. Флаг isBlocked затем блокирует вход и проверяется в token-стратегиях (заблокированный пользователь получает 401).
Примечание:
BlockUserDto(data-access/.../block-user.dto.ts) с полемisBlockedопределён, но текущие эндпоинты его не используют — блокировка задаётся самим путём (/blockvs/unblock) без тела.
Статус проекта менять нельзя
Заголовок раздела «Статус проекта менять нельзя»Смены статуса проекта в API нет — ни отдельным маршрутом, ни полем в теле PATCH /projects/:id. Прежняя матрица переходов PROJECT_STATUS_TRANSITIONS и поле status в AdminProjectUpdateDto сняты вместе с маршрутом PATCH /api/v1/customers/me/projects/:projectId/status в project-api.
Это ломающее изменение API: клиент, дёргавший смену статуса, получит
400на неизвестное поле (forbidNonWhitelisted) либо404на снятом маршруте.
Причина: ProjectStatus переработан (10 значений вместо 8, плюс подстадия contractingStage), и переход между ними — не свойство одного сервиса, а машина состояний с журналом StateTransition, актором и основанием. Проектная модель переходов — step-3-state-machines.md. AdminProjectService.updateOneById теперь только проверяет существование проекта и сохраняет переданные поля.
Верификация команды (POST /administrator/teams/:teamId/verify)
Заголовок раздела «Верификация команды (POST /administrator/teams/:teamId/verify)»Команда создаётся в статусе ONBOARDING и до проверки администратором остаётся в нём. Верификация
переводит её в ACTIVE и штампует verifiedAt — это отметка платформы «команду посмотрели», по
которой команда становится видимой дальнейшим сценариям.
Правила — в домене (TeamService.verifyOneByIdOrFail в user-api), admin-api их не дублирует:
- команды нет —
404; - команда не в
ONBOARDING—409: повторная верификация затёрла бы дату первой, а другого источникаverifiedAtнет; - иначе
status = ACTIVE,verifiedAt = now, в лог уходитteam verified.
Статус пишется напрямую: журнала
StateTransition, outbox и обратных переходов у команды пока нет.ONBOARDING → ACTIVE— единственный реализованный переход,SUSPENDEDиARCHIVEDнедостижимы. Подробности домена — в user-api.
Границы бэк-офиса по командам на сегодня:
- верификация онбординга — единственная операция админ-панели над командой; состав команды
доступен только на чтение (
GET /administrator/teams/:teamId); - передачи роли администратора команды в бэк-офисе нет. Домен не даёт уйти последнему
администратору (
409), но если единственный админ недоступен — аккаунт заблокирован или просто брошен, — назначить нового некому: все операции над составом закрыты рольюADMINвнутри самой команды, и обойти это из админ-панели нельзя. Операция придёт с единицейT2.
Мягкое удаление / восстановление проекта
Заголовок раздела «Мягкое удаление / восстановление проекта»DELETE /projects/:id→softDeleteOneById(id, adminId): проверка существования, затемdeletedAt = new Date(), логadmin.project.soft_deleted, ответ204 No Content.PATCH /projects/:id/restore→restoreOneById(id): ищет проект, включая удалённые (findOneByIdIncludingDeleted); если нет —404; иначеdeletedAt = null. Все списки/карточки по умолчанию фильтруютdeletedAt: null.
Зависимости
Заголовок раздела «Зависимости»- NestJS (
@nestjs/common,@nestjs/core,@nestjs/platform-express,@nestjs/swagger,@nestjs/passport,passport-jwt). - Prisma —
@crewsforge-back/apis/utils/prisma-client(PrismaClientService,PrismaClientModule); моделиUser,Project, ролиuserOnRole/role. - Транзакции/контекст —
nestjs-cls,@nestjs-cls/transactional,@nestjs-cls/transactional-adapter-prisma. - Общие утилиты —
@crewsforge-back/apis/shared:AdminAccessTokenGuard,AdministratorGuard,OperatorGuard,ArbiterGuard,GetTokenPayload,AdminTokenPayload,MapperService,ADMIN_ACCESS_TOKEN_STRATEGY_NAME,PrismaExceptionFilter, декоратор@Trim, константы платформенных ролей (PLATFORM_ROLE_TYPES,ADMIN_ROLE_ID,OPERATOR_ROLE_ID,ARBITER_ROLE_ID). - Конфиг —
@crewsforge-back/apis/configs/shared/app(AppConfigModule, порт) и@crewsforge-back/apis/configs/auth-api/jwt-admin(JwtAdminConfigService, используется в стратегиях admin-auth). - Auth — стратегии
jwt-admin-access/jwt-admin-refreshизauth-api/features/admin-auth; типTokenDecodePayloadизauth-api/features/token. - Внутренние фичи —
admin-api/data-access(DTO и константы),admin-project(используется вAdminUserDetailResponseDto). - user-api —
apis/providers/user-api/features/team(TeamCoreModule,TeamService) иapis/providers/user-api/data-access(TeamExternalCaseDto): домен команд admin-api не повторяет, а переиспользует.
Ключевые файлы
Заголовок раздела «Ключевые файлы»apps/admin-api/src/main.ts— bootstrap, порт3002, префиксapi/v1, Swagger, пайпы/фильтры.apps/admin-api/src/app/app.module.ts— сборка приложения:AppConfigModule, CLS/транзакции,AdminAuthStrategiesModule,AdminContactModule,AdminProjectModule,AdminTeamModule,AdminUserModule,WorkspaceModule.libs/apis/providers/admin-api/features/workspace/src/lib/{operator,administrator,arbiter}-workspace.controller.ts— три точки входа/{роль}/me..../workspace/src/lib/workspace.service.ts— общий для трёх контроллеров: выводит платформенную роль пользователя из базы..../workspace/src/lib/dto/workspace-me-response.dto.ts— контракт ответа{ userId, email, platformRole }.libs/apis/providers/admin-api/features/admin-user/src/lib/admin-user.controller.ts— маршрутыusers, гвард..../admin-user/src/lib/admin-user.service.ts— блокировка/разблокировка, поиск,getAdminMe..../admin-user/src/lib/admin-user.repository.ts— Prisma-запросы по пользователям..../admin-user/src/lib/dto/*—admin-me-response,admin-user-response,admin-user-detail-response,admin-user-role.libs/apis/providers/admin-api/features/admin-project/src/lib/admin-project.controller.ts— маршрутыprojects..../admin-project/src/lib/admin-project.service.ts— редактирование, soft-delete/restore..../admin-project/src/lib/admin-project.repository.ts— Prisma-запросы по проектам (фильтрdeletedAt)..../admin-project/src/lib/dto/*—admin-project-response,admin-project-owner,admin-project-update.libs/apis/providers/admin-api/features/admin-team/src/lib/admin-team.controller.ts— маршрутыadministrator/teams..../admin-team/src/lib/admin-team.service.ts— обёртка надTeamService: список, карточка с составом, верификация..../admin-team/src/lib/dto/*—admin-team-query,admin-team-response(карточка, состав, карточка с составом).libs/apis/providers/admin-api/data-access/src/lib/constants/admin-api.constants.ts—DEFAULT_PAGE_SIZE,MAX_PAGE_SIZE,ADMIN_ROUTE_PREFIX..../data-access/src/lib/dto/*—admin-user-query,admin-project-query,block-user,update-project-status.libs/apis/shared/src/lib/guards/admin-access-token.guard.ts— аутентификация админ-эндпоинтов.libs/apis/shared/src/lib/guards/{platform-role,operator,administrator,arbiter}.guard.ts— guard’ы рабочих мест.libs/apis/configs/auth-api/jwt-admin/**— env, конфиг и сервис доступа кjwt-admin-секретам.
Точки расширения
Заголовок раздела «Точки расширения»- Новая поверхность рабочего места (очередь задач, разбор, спор): добавить фичу
admin-api/features/<name>(Controller → Service → Repository), навесить@UseGuards(AdminAccessTokenGuard, <Operator|Administrator|Arbiter>Guard)и подключить модуль вapps/admin-api/src/app/app.module.ts. Базовый путь контроллера и есть скоуп:operator/...,administrator/...,arbiter/.... - Новая платформенная роль: значение в
RoleType(миграция), строка вPLATFORM_ROLE_TYPESи id вroles.constants.ts, пересоздание частичного индексаusers_on_roles_single_platform_roleмиграцией, новый наследникPlatformRoleGuard, роль в сидеplatform-role.seed.ts. - Тело для block/unblock. Готовый
BlockUserDtoпозволяет при необходимости заменить два пути (/block,/unblock) однимPATCH /users/:idс полемisBlocked. - Пагинация. Все списки строятся на
page/perPageиз data-access; общий формат ответа{ data, total, page, perPage }— удобная точка для единого пагинационного слоя. КонстантаMAX_PAGE_SIZE = 100уже задана в data-access (в DTO лимит продублирован через@Max(100)). - Фильтры/поиск. Расширяются в
service.findAllчерез сборкуPrisma.*WhereInput— новые поля добавляются в query-DTO и в маппингwhere.
- Приложение:
apps/admin-api, порт3002(envADMIN_API_PORT), префиксapi/v1, Swagger наapi/v1/docs; логика — в фичахlibs/apis/providers/admin-api. - Эндпоинты (users):
GET /users,GET /users/me,GET /users/:id,PATCH /users/:id/block,PATCH /users/:id/unblock. - Эндпоинты (projects):
GET /projects,GET /projects/:id,PATCH /projects/:id,DELETE /projects/:id,PATCH /projects/:id/restore; списки поддерживаютpage/perPageи фильтры (users:role,isBlocked,search; projects:status,category,search). Обе поверхности закрытыAdministratorGuard. - Авторизация: аутентификация —
AdminAccessTokenGuard(passport-стратегияjwt-admin-access, отдельные секретыJWT_ADMIN_*, проверка регистрации токена и блокировки пользователя); авторизация — платформенный guard поверх claim’аplatformRole. - Эндпоинты (рабочие места):
GET /operator/me,GET /administrator/me,GET /arbiter/me— ответ{ userId, email, platformRole }, чужое рабочее место даёт403. - Эндпоинты (команды):
GET /administrator/teams,GET /administrator/teams/:teamId,POST /administrator/teams/:teamId/verify— подAdministratorGuard; логика вTeamServiceиз user-api, повторная верификация даёт409. - Эндпоинты (лендинг):
POST /contact/lead,POST /contact/support— публичные, без guard’ов. - Платформенная роль: claim
platformRole(ADMIN | OPERATOR | ARBITER) выводится изUserOnRoleпри каждом выпуске пары токенов, включая refresh; совмещение двух ролей отвергается и на логине (403), и уникальным индексом БД. - Ключевые операции: блокировка пользователя (запрет блокировать ADMIN, защита от повторной блокировки), редактирование проекта, мягкое удаление и восстановление (
deletedAt). Смены статуса проекта в API нет. - Заготовка:
BlockUserDtoопределён в data-access, но текущими контроллерами не используется.UpdateProjectStatusDtoтам же остался без потребителя.