Project API — проекты
Слой:
libs/apis/providers/project-api· Приложение:apps/project-api
Назначение
Заголовок раздела «Назначение»Сервис проектов отвечает за проекты заказчика (Customer) в кабинете: заведение черновика, чтение,
правку и удаление. Весь API работает в контексте текущего заказчика — маршруты смонтированы под
customers/me/projects, а доступ ограничен токеном заказчика.
Ключевые ответственности:
- CRUD над проектами заказчика (create / list / read / update / delete);
- защита инвариантов домена (редактировать и удалять можно только
DRAFT); - при создании проекта — заведение сопутствующего Walrider-треда (AI-ассистент).
Статус проекта сервис не меняет: переходы вынесены из него (см. «Статус проекта» ниже).
Приложение apps/project-api — тонкая обёртка NestJS, вся бизнес-логика вынесена в библиотеку
libs/apis/providers/project-api.
Доменная модель проекта
Заголовок раздела «Доменная модель проекта»Полное описание модели Project — в database-schema:
кроме title/description/category/status она несёт валюту сделки, вилку бюджета в минорных
единицах, ставку комиссии, назначенную команду и подстадию контрактинга. Ни одно из этих полей
project-api сейчас не читает и не пишет.
Что важно для этого сервиса:
- Владелец. Проект всегда принадлежит одному заказчику (
customerId). Идентификатор берётся из токена (payload.customerId), клиент не передаёт его вручную; все операции чтения и записи проверяют совпадение владельца (ForbiddenException, если проект чужой). СвязьProject → CustomerобъявленаonDelete: Restrict— проект удалить каскадом нельзя. - Мягкое удаление.
deletedAtпроставляется сервисом; репозиторий фильтруетdeletedAt: nullво всех выборках. - Ответ ограничен DTO.
ProjectResponseDtoсобираетсяMapperServiceсо стратегиейexcludeAll, поэтому новые колонки таблицы в ответ не просачиваются: наружу отдаётся ровно то, что помечено@Expose(). - Денежных полей в DTO нет. Суммы хранятся в БД как
BigIntв минорных единицах;Decimalв схеме не используется.
DTO (data-access/src/lib/dtos/project.dto.ts)
Заголовок раздела «DTO (data-access/src/lib/dtos/project.dto.ts)»Базовый ProjectDto описывает поля и валидацию, остальные DTO наследуются от него:
id: string—@IsUUID;title: string—@IsString,MinLength(3),MaxLength(255),@Trim;description: string—@IsString,MinLength(3),@Trim;category?: ProjectCategoryType | null— опционально,@IsEnum(ProjectCategoryType);status: ProjectStatus—@IsEnum(ProjectStatus);customerId: string—@IsUUID;createdAt,updatedAt: Date—@IsDate.
Производные DTO:
ProjectResponseDto—extends ProjectDto(полное представление в ответах);ProjectCreateDto—OmitType(ProjectDto, ['id','createdAt','updatedAt','status','customerId'])→ на вход толькоtitle,description,category?;ProjectUpdateDto—PartialType(OmitType(... те же поля ...))→ все поля необязательны, редактируются толькоtitle,description,category;ProjectListQueryDto— фильтрstatus?, пагинацияpage?(Min(1), default 1),perPage?(Min(1), default 10).
ProjectCategoryType (enum, 9 значений): WEB_DEVELOPMENT, MOBILE_DEVELOPMENT, DESIGN,
MARKETING, COPYWRITING, DATA_SCIENCE, DEV_OPS, QA_TESTING, OTHER.
Статус проекта
Заголовок раздела «Статус проекта»Enum ProjectStatus — 10 значений: DRAFT, AI_CONSULTATION, SPEC_READY, TEAM_MATCHING,
TEAM_PROPOSED, CONTRACTING, ACTIVE, COMPLETED, DISPUTED, CANCELLED. У статуса
CONTRACTING есть подстадия Project.contractingStage.
Сменить статус через API нельзя. Маршрут PATCH /api/v1/customers/me/projects/:projectId/status,
DTO ProjectUpdateStatusDto и таблица переходов PROJECT_STATUS_TRANSITIONS сняты; новый проект
по-прежнему стартует в DRAFT (@default(DRAFT) на уровне БД), и это единственный статус, который
сервис проставляет. Статус читается — в фильтре списка и в ответе.
Это ломающее изменение API: клиент, вызывавший
PATCH .../status, получает404.
Причина не в том, что переходы «пока не сделаны», а в том, что прежняя матрица описывала другую
модель продукта. В целевой каждый переход — событие с актором, основанием и записью в журнале
StateTransition, часть переходов автоматические (по таймеру или по событию денежного контура), и
инициирует их не заказчик. Такой переход не помещается в PATCH от владельца проекта. Проектная
модель — step-3-state-machines.md.
HTTP API
Заголовок раздела «HTTP API»Контроллер ProjectController — @Controller({ path: 'customers/me/projects', version: '1' }).
Все маршруты защищены @UseGuards(CustomerGuard) и @ApiBearerAuth(CUSTOMER_ACCESS_TOKEN_STRATEGY_NAME).
С учётом глобального префикса api и URI-версионирования полный путь имеет вид
/api/v1/customers/me/projects....
| Метод | Путь (полный) | Guard | Описание | DTO вход → выход |
|---|---|---|---|---|
| POST | /api/v1/customers/me/projects | CustomerGuard | Создать проект (статус DRAFT) + Walrider-тред | ProjectCreateDto → ProjectResponseDto |
| GET | /api/v1/customers/me/projects | CustomerGuard | Список проектов заказчика с пагинацией и фильтром по статусу | ProjectListQueryDto → paginate ProjectResponseDto |
| GET | /api/v1/customers/me/projects/:projectId | CustomerGuard | Получить проект по id (проверка владельца) | — → ProjectResponseDto |
| PATCH | /api/v1/customers/me/projects/:projectId | CustomerGuard | Обновить проект — только DRAFT | ProjectUpdateDto → ProjectResponseDto |
| DELETE | /api/v1/customers/me/projects/:projectId | CustomerGuard | Удалить проект — только DRAFT (soft-delete через deletedAt) | — → ProjectResponseDto |
Детали поведения (ProjectService):
create—@Transactional(): создаёт проект (customer.connect), затем в той же транзакции Walrider-тред; статус выставляется БД по умолчанию (DRAFT).findMany— фильтрwhere.customerId, опциональноwhere.status, сортировкаcreatedAt desc, пагинация (page/perPage).findOneByIdOrFail—NotFoundExceptionесли нет,ForbiddenExceptionесли чужой.updateOneById— если статус ≠DRAFT→BadRequestException('Only DRAFT projects can be updated'); обновляются только переданные поляtitle/description/category.deleteOneById— если статус ≠DRAFT→BadRequestException('Only DRAFT projects can be deleted'). Репозиторий работает с soft-delete:findUnique/findManyфильтруютdeletedAt: null.
Ответы маппятся через MapperService (toResponse / toPaginateResponse) в ProjectResponseDto.
Интеграция с Walrider
Заголовок раздела «Интеграция с Walrider»При создании проекта ProjectService.create в рамках одной транзакции вызывает
WalriderThreadRepository.create, заводя запись WalriderThread, привязанную к заказчику и проекту
(customer.connect + project.connect). Связь Project ↔ WalriderThread — 1:1
(projectId @unique). WalriderThreadRepository в этом сервисе — тонкий адаптер (create,
findUnique) поверх транзакционного Prisma-хоста; детальная логика AI-ассистента (треды, сообщения,
состояния, внешний externalThreadId) описана отдельно — см. docs/03-services/ai/walrider.md.
Зависимости
Заголовок раздела «Зависимости»@nestjs/*,@nestjs/swagger— HTTP-слой, версионирование, документация.@prisma/client— типыPrisma,ProjectStatus,ProjectCategoryType, модельProject.@nestjs-cls/transactional+@nestjs-cls/transactional-adapter-prisma— декларативные транзакции (@Transactional,TransactionHost); настраиваются вAppModuleчерезClsPluginTransactional.@crewsforge-back/apis/utils/prisma-client—PrismaClientModule/PrismaClientService.@crewsforge-back/apis/shared—CustomerGuard,GetTokenPayload,MapperService,PrismaExceptionFilter,Trim, утилиты пагинации (getPaginate,toPaginateResponse), константы стратегий токенов.@crewsforge-back/apis/configs/shared/app—AppConfigService(порт из envAPP_PORT).@crewsforge-back/apis/configs/auth-api/jwt-customer+@nestjs/jwt— валидация токена заказчика.@crewsforge-back/apis/providers/auth-api/features/token— типTokenPayload.
Конфигурация приложения (apps/project-api/src/main.ts):
- глобальный префикс
api, URI-версионирование (v1); ValidationPipe(transform,whitelist,strategy: 'excludeAll');PrismaExceptionFilter, CORS с cookie,cookie-parser;- Swagger на
/api(title «Project API»), два bearer-auth: customer и employee; - порт —
AppConfigService.port(envAPP_PORT).
Ключевые файлы
Заголовок раздела «Ключевые файлы»apps/project-api/src/main.ts— bootstrap, порт, Swagger, глобальные пайпы/фильтры.apps/project-api/src/app/app.module.ts— корневой модуль, Cls/Transactional,ProjectModule.libs/apis/providers/project-api/features/project/src/lib/project.controller.ts— маршруты и guard’ы..../features/project/src/lib/project.service.ts— бизнес-логика, проверка владельца, транзакции..../features/project/src/lib/project.repository.ts— доступ к данным, soft-delete, пагинация..../features/project/src/lib/walrider-thread.repository.ts— адаптер Walrider-треда (кратко)..../features/project/src/lib/project.module.ts— сборка модуля, провайдеры..../data-access/src/lib/dtos/project.dto.ts— DTO и валидация.apps/core-api/prisma/schema.prisma— модельProject, enumProjectStatus,ProjectCategoryType.
Точки расширения
Заголовок раздела «Точки расширения»Добавить поле проекта:
- Добавить поле в модель
Projectвschema.prisma, сгенерировать миграцию и Prisma-клиент (см. migrations). Для денег —BigIntв минорных единицах, неDecimal: так устроены все денежные поля схемы. - Добавить поле в
ProjectDtoс валидатором и@Expose(); при необходимости учесть его вProjectCreateDto/ProjectUpdateDto(они строятся черезOmitType/PartialType). - Пробросить поле в
Prisma.ProjectCreateInput/updateвнутриProjectService(вcreateи в spread-обновленииupdateOneById). BigIntне сериализуется в JSON — вProjectResponseDtoзначение приводится к строке через@Transform.
Добавить операцию над проектом: метод сервиса по схеме <глагол><One|Many>[By<Поле>][OrFail],
маршрут в контроллере, проверка владельца через findOneByIdOrFail(id, customerId).
Смена статуса в этот сервис не возвращается точечной правкой: она требует библиотеки машины
состояний с журналом переходов. Отправная точка — проектные документы шага 3 и план стройки
docs/delivery/ROADMAP.md.