import type { CategoryReference, InvalidJsonInputError, Product, ProductData, ProductDraft, ProductPagedSearchResponse, ProductSearchRequest, ProductTypeReference, RequiredFieldError, StateReference, TaxCategoryReference, } from "@commercetools/platform-sdk"; import type { Config } from "#src/config.ts"; import { CommercetoolsError } from "#src/exceptions.ts"; import { getBaseResourceProperties } from "#src/helpers.ts"; import { ReviewStatisticsService } from "#src/lib/review-statistics.ts"; import { ProductSearch } from "#src/product-search.ts"; import { ProductDraftSchema } from "#src/schemas/generated/product.ts"; import type { GetParams, RepositoryContext } from "../abstract.ts"; import { AbstractResourceRepository } from "../abstract.ts"; import { getReferenceFromResourceIdentifier } from "../helpers.ts"; import { ProductUpdateHandler } from "./actions.ts"; import { variantFromDraft } from "./helpers.ts"; export class ProductRepository extends AbstractResourceRepository<"product"> { protected _searchService: ProductSearch; protected _reviewStatisticsService: ReviewStatisticsService; constructor(config: Config) { super("product", config); this.actions = new ProductUpdateHandler(config.storage); this._searchService = new ProductSearch(config); this._reviewStatisticsService = new ReviewStatisticsService(config.storage); this.draftSchema = ProductDraftSchema; } async create( context: RepositoryContext, draft: ProductDraft, ): Promise { if (!draft.masterVariant) { throw new CommercetoolsError( { code: "RequiredField", message: "Missing master variant", field: "masterVariant", }, 400, ); } let productType: ProductTypeReference | undefined; try { productType = await getReferenceFromResourceIdentifier( draft.productType, context.projectKey, this._storage, ); } catch (err) { if (this.config.strict) { throw err; } // For now accept missing product types (but warn) process.emitWarning( `Error resolving product-type '${draft.productType.id}'. This will throw an error in later releases.`, ); productType = { typeId: "product-type", id: draft.productType.id || "", }; } // Resolve Product categories const categoryReferences: CategoryReference[] = []; for (const category of draft.categories ?? []) { if (category) { categoryReferences.push( await getReferenceFromResourceIdentifier( category, context.projectKey, this._storage, ), ); } else { throw new CommercetoolsError( { code: "InvalidJsonInput", message: "Request body does not contain valid JSON.", detailedErrorMessage: "categories: JSON object expected.", }, 400, ); } } // Resolve Tax category let taxCategoryReference: TaxCategoryReference | undefined; if (draft.taxCategory) { taxCategoryReference = await getReferenceFromResourceIdentifier( draft.taxCategory, context.projectKey, this._storage, ); } // Resolve Product State let productStateReference: StateReference | undefined; if (draft.state) { productStateReference = await getReferenceFromResourceIdentifier( draft.state, context.projectKey, this._storage, ); } const variants = await Promise.all( draft.variants?.map((variant, index) => variantFromDraft(context, this._storage, index + 2, variant), ) ?? [], ); const productData: ProductData = { name: draft.name, slug: draft.slug, description: draft.description, attributes: draft.attributes ?? [], categories: categoryReferences, masterVariant: await variantFromDraft( context, this._storage, 1, draft.masterVariant, ), variants, metaTitle: draft.metaTitle, metaDescription: draft.metaDescription, metaKeywords: draft.metaKeywords, searchKeywords: draft.searchKeywords ?? {}, }; const resource: Product = { ...getBaseResourceProperties(context.clientId), key: draft.key, productType: productType, taxCategory: taxCategoryReference, state: productStateReference, masterData: { current: productData, staged: productData, hasStagedChanges: false, published: draft.publish ?? false, }, }; return await this.saveNew(context, resource); } async postProcessResource( context: RepositoryContext, resource: Product, params?: GetParams, ): Promise { // Add review statistics to the product const reviewStatistics = await this._reviewStatisticsService.calculateProductReviewStatistics( context.projectKey, resource.id, ); return { ...resource, reviewRatingStatistics: reviewStatistics, }; } async search( context: RepositoryContext, searchRequest: ProductSearchRequest, ): Promise { return this._searchService.search(context.projectKey, searchRequest); } }