diff --git a/components/controller/src/extensions/marketplace/application/coop-acceptance/marketplace-coop-acceptance.service.ts b/components/controller/src/extensions/marketplace/application/coop-acceptance/marketplace-coop-acceptance.service.ts index 6584da406a7..29faf861903 100644 --- a/components/controller/src/extensions/marketplace/application/coop-acceptance/marketplace-coop-acceptance.service.ts +++ b/components/controller/src/extensions/marketplace/application/coop-acceptance/marketplace-coop-acceptance.service.ts @@ -1,4 +1,4 @@ -import { Inject, Injectable, NotFoundException, Optional } from '@nestjs/common'; +import { BadRequestException, Inject, Injectable, NotFoundException, Optional } from '@nestjs/common'; import { EXTENSION_REPOSITORY, @@ -96,7 +96,21 @@ export class MarketplaceCoopAcceptanceService { ); } - const accepted_at = input.accepted_at ?? new Date().toISOString(); + const now = new Date(); + const accepted_at = input.accepted_at ?? now.toISOString(); + if (input.accepted_at) { + const parsed = new Date(input.accepted_at); + if (Number.isNaN(parsed.getTime())) { + throw new BadRequestException( + `Некорректный формат accepted_at: '${input.accepted_at}' — ожидается ISO-8601` + ); + } + if (parsed.getTime() > now.getTime() + 60_000) { + throw new BadRequestException( + `accepted_at не может быть в будущем (${input.accepted_at})` + ); + } + } const acceptance: ICoopAcceptanceConfig = { accepted: true, document_registry_id: input.document_registry_id, diff --git a/components/controller/src/extensions/marketplace/application/registration/register-marketplace-in-agreement-registry.ts b/components/controller/src/extensions/marketplace/application/registration/register-marketplace-in-agreement-registry.ts index 38316dbf72f..46f90fa1fa6 100644 --- a/components/controller/src/extensions/marketplace/application/registration/register-marketplace-in-agreement-registry.ts +++ b/components/controller/src/extensions/marketplace/application/registration/register-marketplace-in-agreement-registry.ts @@ -18,7 +18,7 @@ import { * создан в document factory — Story 1.7 не выполнена) — port не вызывается, * SignUp не предлагает оферту marketplace; функция возвращает false; * • при реальном `registry_id > 0` — `registerAgreement` × 1 для оферты ЦПП - * «Стол заказов» с типами аккаунтов `individual` + `entrepreneur`. + * «Стол заказов» с типами аккаунтов `individual` + `entrepreneur` + `organization`. * * Идемпотентность гарантируется самим `AgreementRegistryService` (повторный * register с тем же id+extension_name перезаписывает запись, см. док-комментарий @@ -41,7 +41,11 @@ export function registerMarketplaceInAgreementRegistry( title: 'Оферта по целевой потребительской программе «Стол заказов»', checkbox_text: 'Я прочитал и принимаю', link_text: 'оферту по целевой потребительской программе «Стол заказов»', - applicable_account_types: [AccountType.individual, AccountType.entrepreneur], + applicable_account_types: [ + AccountType.individual, + AccountType.entrepreneur, + AccountType.organization, + ], order: 7, extension_name: MARKETPLACE_EXTENSION_NAME, }); diff --git a/components/controller/src/extensions/marketplace/application/resolvers/attribute.resolver.ts b/components/controller/src/extensions/marketplace/application/resolvers/attribute.resolver.ts index 66d3fc32f5a..8be5a6d9980 100644 --- a/components/controller/src/extensions/marketplace/application/resolvers/attribute.resolver.ts +++ b/components/controller/src/extensions/marketplace/application/resolvers/attribute.resolver.ts @@ -1,8 +1,7 @@ import { Resolver, Query, Args } from '@nestjs/graphql'; import { Injectable, UseGuards } from '@nestjs/common'; import { GqlJwtAuthGuard } from '~/application/auth/guards/graphql-jwt-auth.guard'; -import { RolesGuard } from '~/application/auth/guards/roles.guard'; -import { AuthRoles } from '~/application/auth/decorators/auth.decorator'; +import { MarketplaceMembershipGuard } from '../guards/marketplace-membership.guard'; import { AttributeDomainService } from '../../domain/services/attribute-domain.service'; import { AttributeDTO, AttributeGroupDTO, AttributeStatsDTO, DictionaryValueDTO } from '../dto/attribute.dto'; import { SearchAttributesInput } from '../dto/search-attributes-input.dto'; @@ -31,9 +30,13 @@ export class AttributeValidationResult { } /** - * GraphQL резолвер для атрибутов marketplace + * GraphQL резолвер для атрибутов marketplace. + * + * Доступ — пайщикам кооператива (через `MarketplaceMembershipGuard`). + * Story 1.3 / 1.8: словарь атрибутов — закрытый ресурс marketplace. */ @Resolver(() => AttributeDTO) +@UseGuards(GqlJwtAuthGuard, MarketplaceMembershipGuard) @Injectable() export class AttributeResolver { constructor(private readonly attributeService: AttributeDomainService) {} @@ -203,8 +206,6 @@ export class AttributeResolver { name: 'marketplaceAttributeStats', description: 'Получить статистику по атрибутам marketplace', }) - @UseGuards(GqlJwtAuthGuard, RolesGuard) - @AuthRoles(['chairman', 'member']) async getAttributeStats(): Promise { const stats = await this.attributeService.getAttributeStats(); return new AttributeStatsDTO(stats); diff --git a/components/controller/src/extensions/marketplace/application/resolvers/category-tree.resolver.ts b/components/controller/src/extensions/marketplace/application/resolvers/category-tree.resolver.ts index ccbf7b3becd..c6502cf5cf7 100644 --- a/components/controller/src/extensions/marketplace/application/resolvers/category-tree.resolver.ts +++ b/components/controller/src/extensions/marketplace/application/resolvers/category-tree.resolver.ts @@ -1,8 +1,7 @@ import { Resolver, Query, Args, Int } from '@nestjs/graphql'; import { Inject, UseGuards } from '@nestjs/common'; import { GqlJwtAuthGuard } from '~/application/auth/guards/graphql-jwt-auth.guard'; -import { RolesGuard } from '~/application/auth/guards/roles.guard'; -import { AuthRoles } from '~/application/auth/decorators/auth.decorator'; +import { MarketplaceMembershipGuard } from '../guards/marketplace-membership.guard'; import { CategoryTreeService, CATEGORY_TREE_SERVICE } from '../services/category-tree.service'; import { CategoryTreeDomainService, CATEGORY_TREE_DOMAIN_SERVICE } from '../../domain/services/category-tree-domain.service'; import { CategoryDTO, ProductTypeDTO } from '../dto/category-tree.dto'; @@ -13,9 +12,13 @@ import { GetProductTypeByIdInput } from '../dto/get-product-type-by-id-input.dto import { SearchCategoriesInput } from '../dto/search-categories-input.dto'; /** - * GraphQL резолвер для работы с деревом категорий marketplace + * GraphQL резолвер для работы с деревом категорий marketplace. + * + * Доступ — пайщикам кооператива (через `MarketplaceMembershipGuard`). + * Story 1.3 / 1.8: каталог категорий — закрытый ресурс marketplace, не публичный. */ @Resolver(() => CategoryDTO) +@UseGuards(GqlJwtAuthGuard, MarketplaceMembershipGuard) export class CategoryTreeResolver { constructor( @Inject(CATEGORY_TREE_SERVICE) @@ -82,8 +85,6 @@ export class CategoryTreeResolver { name: 'marketplaceGetCategoryTreeStats', description: 'Получить статистику по дереву категорий', }) - @UseGuards(GqlJwtAuthGuard, RolesGuard) - @AuthRoles(['chairman', 'member']) async getCategoryTreeStats(): Promise { return this.categoryTreeService.getCategoryTreeStats(); } diff --git a/components/controller/src/extensions/marketplace/application/resolvers/request.resolver.ts b/components/controller/src/extensions/marketplace/application/resolvers/request.resolver.ts index 9a6dba8cb90..b018899efc4 100644 --- a/components/controller/src/extensions/marketplace/application/resolvers/request.resolver.ts +++ b/components/controller/src/extensions/marketplace/application/resolvers/request.resolver.ts @@ -1,6 +1,7 @@ import { Resolver, Mutation, Query, Args, Int } from '@nestjs/graphql'; import { Injectable, Inject, UseGuards } from '@nestjs/common'; import { RequestDomainService, REQUEST_DOMAIN_SERVICE } from '../../domain/services/request-domain.service'; +import { MarketplaceMembershipGuard } from '../guards/marketplace-membership.guard'; import { RolesGuard } from '~/application/auth/guards/roles.guard'; import { AuthRoles } from '~/application/auth/decorators/auth.decorator'; import { RequestDTO } from '../dto/request.dto'; @@ -21,9 +22,13 @@ import { CurrentUser } from '~/application/auth/decorators/current-user.decorato import type { MonoAccountDomainInterface } from '~/domain/account/interfaces/mono-account-domain.interface'; /** - * GraphQL resolver для работы с заявками marketplace + * GraphQL resolver для работы с заявками marketplace. + * + * Доступ — пайщикам кооператива (через `MarketplaceMembershipGuard`). + * Story 1.3 / 1.8: заявки — закрытый ресурс marketplace, не публичный. */ @Resolver(() => RequestDTO) +@UseGuards(GqlJwtAuthGuard, MarketplaceMembershipGuard) @Injectable() export class RequestResolver { constructor( diff --git a/components/controller/src/extensions/marketplace/domain/services/request-domain.service.ts b/components/controller/src/extensions/marketplace/domain/services/request-domain.service.ts index 65325df7b90..0c4b49187fa 100644 --- a/components/controller/src/extensions/marketplace/domain/services/request-domain.service.ts +++ b/components/controller/src/extensions/marketplace/domain/services/request-domain.service.ts @@ -136,7 +136,7 @@ export class RequestDomainService { parentHash: params.parentHash, coopname: params.coopname, username: params.username, - // braname://TODO: добавить участок поставки / получения + // TODO: добавить участок поставки / получения type: params.type, status: RequestStatus.DRAFT, name: params.name, diff --git a/components/controller/src/extensions/marketplace/marketplace-extension.module.ts b/components/controller/src/extensions/marketplace/marketplace-extension.module.ts index cdd4677f3ec..ab7e2eb7448 100644 --- a/components/controller/src/extensions/marketplace/marketplace-extension.module.ts +++ b/components/controller/src/extensions/marketplace/marketplace-extension.module.ts @@ -20,7 +20,7 @@ import { registerMarketplaceInAgreementRegistry } from './application/registrati /** * Optional-инжектируемый порт файлового хранилища. Имя расширения marketplace * подключается к bucket через `@coopenomics/inter` (см. AR31 в epics.md). - * До merge PR #359 `feat(file-storage)` адаптера ещё нет — поэтому через + * Адаптер file-storage подключается опционально через DI — поэтому через * `@Optional` и опциональный токен. */ export const MARKETPLACE_FILE_STORAGE_PORT = Symbol('MARKETPLACE_FILE_STORAGE_PORT'); @@ -98,13 +98,13 @@ export class MarketplacePlugin extends BaseExtModule { /** * Bucket для хранения изображений Стола заказов и фотографий гарантийного * возврата. Имя — `coop-` (см. AR31). Если адаптер file-storage - * не подключён (PR #359 не вмержен), пропускаем шаг с warn-логом; install - * расширения не падает — оставшиеся шаги выполняются. + * отключён конфигурацией — пропускаем шаг с info-логом; install расширения + * не падает, оставшиеся шаги выполняются. */ private async initBucket(): Promise { if (!this.fileStorage) { - this.logger.warn( - 'File storage не настроен — пропускаем bucket init (PR #359 не вмержен)' + this.logger.info( + 'File storage отключён конфигурацией — пропускаем bucket init' ); return; }