Эпик 1 ревью: guards membership + MEDIUM/LOW #1
+16
-2
@@ -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,
|
||||
|
||||
+6
-2
@@ -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,
|
||||
});
|
||||
|
||||
+6
-5
@@ -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<AttributeStatsDTO> {
|
||||
const stats = await this.attributeService.getAttributeStats();
|
||||
return new AttributeStatsDTO(stats);
|
||||
|
||||
+6
-5
@@ -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<CategoryTreeStatsDTO> {
|
||||
return this.categoryTreeService.getCategoryTreeStats();
|
||||
}
|
||||
|
||||
+6
-1
@@ -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(
|
||||
|
||||
+1
-1
@@ -136,7 +136,7 @@ export class RequestDomainService {
|
||||
parentHash: params.parentHash,
|
||||
coopname: params.coopname,
|
||||
username: params.username,
|
||||
// braname://TODO: добавить участок поставки / получения
|
||||
// TODO: добавить участок поставки / получения
|
||||
|
ant
commented
а почему не добавлен braname?? или позже добавшиь? а почему не добавлен braname?? или позже добавшиь?
|
||||
type: params.type,
|
||||
status: RequestStatus.DRAFT,
|
||||
name: params.name,
|
||||
|
||||
@@ -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-<coopname>` (см. AR31). Если адаптер file-storage
|
||||
* не подключён (PR #359 не вмержен), пропускаем шаг с warn-логом; install
|
||||
* расширения не падает — оставшиеся шаги выполняются.
|
||||
* отключён конфигурацией — пропускаем шаг с info-логом; install расширения
|
||||
* не падает, оставшиеся шаги выполняются.
|
||||
*/
|
||||
private async initBucket(): Promise<void> {
|
||||
if (!this.fileStorage) {
|
||||
this.logger.warn(
|
||||
'File storage не настроен — пропускаем bucket init (PR #359 не вмержен)'
|
||||
this.logger.info(
|
||||
'File storage отключён конфигурацией — пропускаем bucket init'
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user
ты их здесь убрал потому что у тебя в MarketplaceMembershipGuard равнозначная проверка?