Перейти к содержимому

Техническое задание: Сервис администрации

  • Монорепозиторий: NX 22.0
  • Backend: NestJS 11.0 (микросервисная архитектура: auth-api, user-api, project-api, core-api, ai-agent-service)
  • ORM: Prisma 6.18
  • База данных: PostgreSQL
  • Кэш: Redis 5.9
  • Авторизация: Passport.js + JWT (access + refresh), раздельные конфигурации для Customer и Employee
  • Email: Nodemailer (@nestjs-modules/mailer) через SMTP
  • Верификация email: 6-символьный буквенно-цифровой код, хранение в Redis с TTL 900 сек (15 мин), cooldown на повторную отправку 120 сек (2 мин)
  • Очереди: RabbitMQ
  • Шаред-библиотеки: /libs/apis/ — configs, providers, shared (guards, decorators, helpers)
  • Таблица roles уже существует с enum RoleType (значения: CUSTOMER, EMPLOYEE).
  • Связь users ↔ roles реализована через junction-таблицу users_on_roles (many-to-many).
  • Необходимо добавить значение ADMIN в enum RoleType.
  • Админ — это тот же User, но с привязанной ролью ADMIN через users_on_roles.
  • Все админы равнозначны, иерархии нет.
  • В системе уже есть раздельные JWT-конфигурации: JWT_CUSTOMER_* и JWT_EMPLOYEE_*.
  • Каждый тип пользователя имеет свои Passport-стратегии (CustomerAccessTokenStrategy, EmployeeAccessTokenStrategy и т.д.) и Guard’ы (CustomerAccessTokenGuard, EmployeeAccessTokenGuard).
  • Для админа необходимо создать аналогичную инфраструктуру: JWT-конфигурацию, стратегии, guard’ы.

  • Список разрешённых доменов хранится в .env (переменная: ADMIN_ALLOWED_DOMAINS=company.com,company.io).
  • При попытке регистрации система проверяет, что домен email-адреса входит в список разрешённых.
  • Если домен не совпадает — отказ с понятным сообщением об ошибке.
  1. Админ вводит корпоративный email.
  2. Система проверяет домен email по списку разрешённых.
  3. Система проверяет, что пользователь с таким email ещё не зарегистрирован.
  4. На email отправляется код подтверждения — переиспользовать существующий механизм:
    • Генерация 6-символьного кода через generateRandomCode() из random-code.helper.ts.
    • Хранение в Redis с ключом ADMIN_VERIFICATION_CODE:email={email} и TTL 900 сек.
    • Cooldown на повторную отправку через ключ ADMIN_VERIFICATION_RESEND_CODE:email={email} с TTL 120 сек.
    • Отправка через EmailSenderService (добавить новый шаблон admin_email_verification).
  5. Админ вводит полученный код.
  6. Система верифицирует код (проверка в Redis, удаление после использования).
  7. При успешной верификации:
    • Создаётся запись User (без пароля — поля passwordHash/passwordSalt null или пустые).
    • Создаётся связь через UserOnRole с ролью ADMIN.
    • Создаётся Session с fingerprint устройства (аналогично customer auth).
    • Создаются Token записи в БД.
  8. Возвращается пара JWT-токенов (access + refresh).
  • Пароль не используется. Вся аутентификация — passwordless (email + код).
  • Не нужно создавать записи в таблицах customers или employees — админ не является ни тем, ни другим.

  1. Админ вводит свой корпоративный email.
  2. Система проверяет, что пользователь с таким email существует и имеет роль ADMIN (через users_on_roles).
  3. На email отправляется код подтверждения (тот же механизм, что при регистрации).
  4. Админ вводит код.
  5. При успешной верификации:
    • Создаётся/обновляется Session (по fingerprint из заголовка X-Fingerprint).
    • Генерируются JWT-токены и сохраняются в таблицу tokens.
    • Возвращается пара JWT-токенов.
  • Создать отдельную JWT-конфигурацию для админа (по аналогии с JwtCustomerConfig / JwtEmployeeConfig):
    • JWT_ADMIN_ACCESS_SECRET_KEY
    • JWT_ADMIN_ACCESS_EXPIRES_IN (default: 3600)
    • JWT_ADMIN_REFRESH_SECRET_KEY
    • JWT_ADMIN_REFRESH_EXPIRES_IN (default: 7200)
  • Payload токена (по аналогии с TokenPayload):
    {
    userId: string
    roles: ['ADMIN']
    iat: number
    exp: number
    jti: string // UUID, уникальный идентификатор токена
    }
  • Refresh-токен — стандартный флоу через POST /admin/auth/refresh с заголовками X-Fingerprint и X-Session-Id.

Создать по аналогии с существующей инфраструктурой (customer/employee):

  • AdminAccessTokenStrategy — Passport-стратегия для валидации access-токена.
  • AdminRefreshTokenStrategy — Passport-стратегия для валидации refresh-токена.
  • AdminAccessTokenGuard — Guard для защиты эндпоинтов (использует стратегию).
  • AdminRefreshTokenGuard — Guard для эндпоинта обновления токена.

Расположение:

  • Стратегии: /libs/apis/providers/auth-api/features/admin-auth/src/lib/strategies/
  • Guard’ы: /libs/apis/shared/src/lib/guards/
  • Обычные пользователи (Customer/Employee) НЕ имеют доступа к админским эндпоинтам.
  • Админы имеют доступ ко всем админским эндпоинтам на равных правах (без иерархии).
  • Guard проверяет JWT-подпись отдельным секретом (JWT_ADMIN_ACCESS_SECRET_KEY), что делает токены Customer/Employee невалидными для админских эндпоинтов.

  • Админ может видеть ВСЕ проекты всех пользователей.
  • GET /admin/projects — список всех проектов с пагинацией (переиспользовать getPaginate() / toPaginateResponse() из paginate.helper.ts), фильтрацией по status (ProjectStatus enum) и category (ProjectCategoryType enum), поиском по title.
  • GET /admin/projects/:id — детальная информация о проекте.
  • В ответе должна содержаться информация о владельце (customer → user: id, email).
  • Админ может редактировать любой проект любого пользователя.
  • PATCH /admin/projects/:id — обновление полей проекта.
  • Редактируемые поля: title, description, category, status (с учётом допустимых переходов из PROJECT_STATUS_TRANSITIONS).
  • Админ может удалить любой проект.
  • DELETE /admin/projects/:id.
  • Реализовать soft delete: добавить поле deletedAt (DateTime, nullable) в модель Project. При удалении проставлять текущую дату. Все запросы на выборку должны фильтровать deletedAt = null.
  • Админ может заблокировать (забанить) пользователя.
  • PATCH /admin/users/:id/block — заблокировать пользователя.
  • PATCH /admin/users/:id/unblock — разблокировать пользователя.
  • Добавить поле isBlocked (Boolean, default: false) в модель User.
  • Заблокированный пользователь не может авторизоваться — при попытке входа возвращать понятное сообщение об ошибке.
  • Проверку isBlocked добавить в существующие guard’ы/стратегии (CustomerAccessTokenStrategy, EmployeeAccessTokenStrategy) — при каждом запросе проверять флаг в БД.
  • Нельзя блокировать других админов.
  • GET /admin/users — список всех пользователей с пагинацией (переиспользовать getPaginate()), фильтрацией по роли (RoleType), статусу блокировки (isBlocked), поиском по email.
  • GET /admin/users/:id — детальная информация о пользователе (profile, роли через users_on_roles, isBlocked, список проектов).

МетодПутьОписание
POST/admin/auth/registerНачать регистрацию (email → проверка домена → отправка кода)
POST/admin/auth/register/verifyПодтвердить код → создание аккаунта → JWT
POST/admin/auth/loginНачать вход (email → проверка существования + роль ADMIN → отправка кода)
POST/admin/auth/login/verifyПодтвердить код входа → JWT
POST/admin/auth/refreshОбновить access-токен (заголовки: X-Fingerprint, X-Session-Id)
МетодПутьОписание
GET/admin/projectsСписок всех проектов (пагинация: page, perPage; фильтры: status, category; поиск: search)
GET/admin/projects/:idДетали проекта (включая владельца)
PATCH/admin/projects/:idРедактирование проекта
DELETE/admin/projects/:idSoft delete проекта
МетодПутьОписание
GET/admin/usersСписок пользователей (пагинация, фильтры: role, isBlocked; поиск по email)
GET/admin/users/:idДетали пользователя (профиль, роли, проекты)
PATCH/admin/users/:id/blockЗаблокировать пользователя
PATCH/admin/users/:id/unblockРазблокировать пользователя

Файл: /apps/core-api/prisma/schema.prisma

enum RoleType {
CUSTOMER
EMPLOYEE
ADMIN // <-- добавить
}
model User {
// ... существующие поля
isBlocked Boolean @default(false) @map("is_blocked")
// passwordHash и passwordSalt должны стать nullable (для passwordless admin)
passwordHash String? @map("password_hash")
passwordSalt String? @map("password_salt")
}
model Project {
// ... существующие поля
deletedAt DateTime? @map("deleted_at")
}

Добавить в /libs/apis/shared/src/lib/constants/:

ADMIN_VERIFICATION_CODE = 'ADMIN_VERIFICATION_CODE:email={email}'
ADMIN_VERIFICATION_RESEND_CODE = 'ADMIN_VERIFICATION_RESEND_CODE:email={email}'

TTL: код — 900 сек, cooldown — 120 сек (аналогично customer).


8. Структура кода (новые модули/библиотеки)

Заголовок раздела «8. Структура кода (новые модули/библиотеки)»

Следуя архитектуре монорепозитория:

Новое приложение (опционально) или модуль в auth-api

Заголовок раздела «Новое приложение (опционально) или модуль в auth-api»

Рекомендуется добавить admin auth как часть существующего auth-api, по аналогии с customer-auth и employee-auth:

libs/apis/
├── configs/auth-api/
│ └── jwt-admin/ # JWT конфигурация для админа
├── providers/auth-api/
│ └── features/
│ └── admin-auth/ # Регистрация, логин, верификация, refresh
│ └── src/lib/
│ ├── strategies/
│ │ ├── admin-access-token.strategy.ts
│ │ └── admin-refresh-token.strategy.ts
│ ├── admin-auth.controller.ts
│ ├── admin-auth.service.ts
│ └── admin-auth.module.ts
├── providers/admin-api/ # Админские CRUD-эндпоинты
│ └── features/
│ ├── admin-project/ # GET/PATCH/DELETE /admin/projects
│ └── admin-user/ # GET/PATCH /admin/users
└── shared/src/lib/
└── guards/
├── admin-access-token.guard.ts
└── admin-refresh-token.guard.ts

Новое приложение admin-api (или контроллеры в auth-api / отдельный app)

Заголовок раздела «Новое приложение admin-api (или контроллеры в auth-api / отдельный app)»

Для CRUD-эндпоинтов (projects, users) рекомендуется создать отдельное NX-приложение admin-api со своим портом, либо добавить контроллеры в существующий auth-api под префиксом /admin.


Добавить в .env и .env.example:

# Admin JWT
JWT_ADMIN_ACCESS_SECRET_KEY=
JWT_ADMIN_ACCESS_EXPIRES_IN=3600
JWT_ADMIN_REFRESH_SECRET_KEY=
JWT_ADMIN_REFRESH_EXPIRES_IN=7200
# Admin allowed email domains
ADMIN_ALLOWED_DOMAINS=company.com,company.io

  • Все админские эндпоинты защищены AdminAccessTokenGuard — без валидного JWT с ролью ADMIN доступ запрещён.
  • JWT для админа подписывается отдельным секретом (JWT_ADMIN_ACCESS_SECRET_KEY), что изолирует админские токены от customer/employee токенов.
  • Проверка isBlocked при каждом запросе авторизованного пользователя (добавить в существующие Passport-стратегии).
  • Rate limiting на эндпоинты отправки кода подтверждения (переиспользовать существующие cooldown-механизмы через Redis).
  • Код подтверждения — одноразовый, удаляется из Redis после успешного использования.
  • Админ не может заблокировать другого админа.
  • Логирование действий админа — рекомендуется для аудита (вне скоупа MVP).

11. Что переиспользовать из существующего кода

Заголовок раздела «11. Что переиспользовать из существующего кода»
ЧтоОткуда
Генерация кода верификацииlibs/apis/shared/src/lib/helpers/random-code.helper.tsgenerateRandomCode()
Redis TTL-константыlibs/apis/shared/src/lib/constants/ — добавить новые по аналогии
Email-отправкаlibs/apis/providers/email-sender-api/EmailSenderService
JWT генерация/валидацияlibs/apis/providers/auth-api/features/token/TokenCustomerService как образец
Passport-стратегииlibs/apis/providers/auth-api/features/customer-auth/src/lib/strategies/ — как образец
Guard’ыlibs/apis/shared/src/lib/guards/ — как образец
Пагинацияlibs/apis/shared/src/lib/helpers/paginate.helper.tsgetPaginate(), toPaginateResponse()
DTO-маппингlibs/apis/shared/src/lib/services/mapper.service.ts
Декораторы@GetTokenPayload(), @HeaderFingerprint(), @HeaderSessionId()
Сессииlibs/apis/providers/auth-api/features/session/

Создать Prisma-миграцию, которая:

  1. Добавляет ADMIN в enum RoleType.
  2. Добавляет колонку is_blocked (boolean, default false) в таблицу users.
  3. Делает password_hash и password_salt nullable в таблице users.
  4. Добавляет колонку deleted_at (timestamp, nullable) в таблицу projects.
  5. Вставляет запись роли ADMIN в таблицу roles.

Файл миграции: /apps/core-api/prisma/migrations/<timestamp>_add_admin_role_and_user_blocking/


  • Фронтенд / админ-панель (только API).
  • Иерархия админов (суперадмин).
  • Блокировка отдельных проектов (только бан пользователя целиком).
  • Управление списком разрешённых доменов через API (хранится в .env).
  • Логирование действий админа (аудит-лог).
  • Создание отдельного admin-api микросервиса (решение принимается при реализации).