feat: Make tags unique, clean up searchability of product entities (#7014)
This commit is contained in:
@@ -658,8 +658,7 @@ medusaIntegrationTestRunner({
|
||||
}
|
||||
})
|
||||
|
||||
// TODO: Enforce tag uniqueness in product module
|
||||
it.skip("returns a list of products with tags", async () => {
|
||||
it("returns a list of products with tags", async () => {
|
||||
const response = await api.get(
|
||||
`/admin/products?tags[]=${baseProduct.tags[0].id}`,
|
||||
adminHeaders
|
||||
@@ -668,26 +667,25 @@ medusaIntegrationTestRunner({
|
||||
expect(response.status).toEqual(200)
|
||||
expect(response.data.products).toHaveLength(2)
|
||||
expect(response.data.products).toEqual(
|
||||
expect.arrayContaining(
|
||||
[
|
||||
expect.objectContaining({
|
||||
id: baseProduct.id,
|
||||
tags: [
|
||||
expect.objectContaining({ id: baseProduct.tags[0].id }),
|
||||
],
|
||||
}),
|
||||
],
|
||||
expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
id: baseProduct.id,
|
||||
tags: expect.arrayContaining([
|
||||
expect.objectContaining({ id: baseProduct.tags[0].id }),
|
||||
]),
|
||||
}),
|
||||
expect.objectContaining({
|
||||
id: publishedProduct.id,
|
||||
// It should be the same tag instance in both products
|
||||
tags: [expect.objectContaining({ id: baseProduct.tags[0].id })],
|
||||
})
|
||||
)
|
||||
tags: expect.arrayContaining([
|
||||
expect.objectContaining({ id: baseProduct.tags[0].id }),
|
||||
]),
|
||||
}),
|
||||
])
|
||||
)
|
||||
})
|
||||
|
||||
// TODO: Enforce tag uniqueness in product module
|
||||
it.skip("returns a list of products with tags in a collection", async () => {
|
||||
it("returns a list of products with tags in a collection", async () => {
|
||||
const response = await api.get(
|
||||
`/admin/products?collection_id[]=${baseCollection.id}&tags[]=${baseProduct.tags[0].id}`,
|
||||
adminHeaders
|
||||
@@ -700,7 +698,9 @@ medusaIntegrationTestRunner({
|
||||
expect.objectContaining({
|
||||
id: baseProduct.id,
|
||||
collection_id: baseCollection.id,
|
||||
tags: [expect.objectContaining({ id: baseProduct.tags[0].id })],
|
||||
tags: expect.arrayContaining([
|
||||
expect.objectContaining({ id: baseProduct.tags[0].id }),
|
||||
]),
|
||||
}),
|
||||
])
|
||||
)
|
||||
@@ -2655,8 +2655,7 @@ medusaIntegrationTestRunner({
|
||||
expect(response2.data.id).toEqual(res.data.product.id)
|
||||
})
|
||||
|
||||
// TODO: We just need to return the correct error message
|
||||
it.skip("should fail when creating a product with a handle that already exists", async () => {
|
||||
it("should fail when creating a product with a handle that already exists", async () => {
|
||||
// Lets try to create a product with same handle as deleted one
|
||||
const payload = {
|
||||
title: baseProduct.title,
|
||||
@@ -2675,7 +2674,10 @@ medusaIntegrationTestRunner({
|
||||
await api.post("/admin/products", payload, adminHeaders)
|
||||
} catch (error) {
|
||||
expect(error.response.data.message).toMatch(
|
||||
"Product with handle test-product already exists."
|
||||
breaking(
|
||||
() => "Product with handle base-product already exists.",
|
||||
() => "Product with handle: base-product already exists."
|
||||
)
|
||||
)
|
||||
}
|
||||
})
|
||||
|
||||
@@ -84,6 +84,7 @@ export const AdminGetProductOptionsParams = createFindParams({
|
||||
limit: 50,
|
||||
}).merge(
|
||||
z.object({
|
||||
q: z.string().optional(),
|
||||
id: z.union([z.string(), z.array(z.string())]).optional(),
|
||||
title: z.string().optional(),
|
||||
$and: z.lazy(() => AdminGetProductsParams.array()).optional(),
|
||||
|
||||
@@ -30,7 +30,7 @@ export const productsData = [
|
||||
tags: [
|
||||
{
|
||||
id: "tag-3",
|
||||
value: "Germany",
|
||||
value: "Netherlands",
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
@@ -825,6 +825,7 @@ moduleIntegrationTestRunner({
|
||||
|
||||
const productTwoData = buildProductAndRelationsData({
|
||||
collection_id: productCollectionTwo.id,
|
||||
tags: [],
|
||||
})
|
||||
|
||||
await service.create([productOneData, productTwoData])
|
||||
|
||||
@@ -341,7 +341,7 @@ moduleIntegrationTestRunner({
|
||||
|
||||
const productOptions = await service.list(
|
||||
{
|
||||
title: "US%",
|
||||
q: "US%",
|
||||
},
|
||||
{
|
||||
relations: ["product"],
|
||||
|
||||
@@ -99,7 +99,7 @@ moduleIntegrationTestRunner({
|
||||
})
|
||||
|
||||
it("list product tags by value matching string", async () => {
|
||||
const tagsResults = await service.list({ value: "united kingdom" })
|
||||
const tagsResults = await service.list({ q: "united kingdom" })
|
||||
|
||||
expect(tagsResults).toEqual([
|
||||
expect.objectContaining({
|
||||
|
||||
@@ -24,6 +24,7 @@ import { SqlEntityManager } from "@mikro-orm/postgresql"
|
||||
import { createProductCategories } from "../../../__fixtures__/product-category"
|
||||
import { Modules } from "@medusajs/modules-sdk"
|
||||
import { moduleIntegrationTestRunner, SuiteOptions } from "medusa-test-utils"
|
||||
import { ProductTag } from "../../../../src/models"
|
||||
|
||||
jest.setTimeout(30000)
|
||||
|
||||
|
||||
@@ -48,8 +48,7 @@ export class InitialSetup20240315083440 extends Migration {
|
||||
|
||||
this.addSql('create table if not exists "product_tag" ("id" text not null, "value" text not null, "metadata" jsonb null, "created_at" timestamptz not null default now(), "updated_at" timestamptz not null default now(), "deleted_at" timestamptz null, constraint "product_tag_pkey" primary key ("id"));');
|
||||
|
||||
// TODO: We need to modify upsertWithReplace to handle unique constraints
|
||||
// this.addSql('create unique index if not exists "IDX_tag_value_unique" on "product_tag" (value) where deleted_at is null;')
|
||||
this.addSql('create unique index if not exists "IDX_tag_value_unique" on "product_tag" (value) where deleted_at is null;')
|
||||
this.addSql('create index if not exists "IDX_product_tag_deleted_at" on "product_tag" ("deleted_at");');
|
||||
|
||||
this.addSql('create table if not exists "product_type" ("id" text not null, "value" text not null, "metadata" json null, "created_at" timestamptz not null default now(), "updated_at" timestamptz not null default now(), "deleted_at" timestamptz null, constraint "product_type_pkey" primary key ("id"));');
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import {
|
||||
DALUtils,
|
||||
Searchable,
|
||||
createPsqlIndexStatementHelper,
|
||||
generateEntityId,
|
||||
} from "@medusajs/utils"
|
||||
@@ -35,6 +36,7 @@ class ProductOption {
|
||||
@PrimaryKey({ columnType: "text" })
|
||||
id!: string
|
||||
|
||||
@Searchable()
|
||||
@Property({ columnType: "text" })
|
||||
title: string
|
||||
|
||||
|
||||
@@ -12,6 +12,7 @@ import {
|
||||
|
||||
import {
|
||||
DALUtils,
|
||||
Searchable,
|
||||
createPsqlIndexStatementHelper,
|
||||
generateEntityId,
|
||||
} from "@medusajs/utils"
|
||||
@@ -33,6 +34,7 @@ class ProductTag {
|
||||
@PrimaryKey({ columnType: "text" })
|
||||
id!: string
|
||||
|
||||
@Searchable()
|
||||
@Property({ columnType: "text" })
|
||||
value: string
|
||||
|
||||
|
||||
@@ -10,6 +10,7 @@ import {
|
||||
|
||||
import {
|
||||
DALUtils,
|
||||
Searchable,
|
||||
createPsqlIndexStatementHelper,
|
||||
generateEntityId,
|
||||
} from "@medusajs/utils"
|
||||
@@ -30,6 +31,7 @@ class ProductType {
|
||||
@PrimaryKey({ columnType: "text" })
|
||||
id!: string
|
||||
|
||||
@Searchable()
|
||||
@Property({ columnType: "text" })
|
||||
value: string
|
||||
|
||||
|
||||
@@ -85,12 +85,15 @@ class ProductVariant {
|
||||
@Property({ columnType: "text", nullable: true })
|
||||
sku?: string | null
|
||||
|
||||
@Searchable()
|
||||
@Property({ columnType: "text", nullable: true })
|
||||
barcode?: string | null
|
||||
|
||||
@Searchable()
|
||||
@Property({ columnType: "text", nullable: true })
|
||||
ean?: string | null
|
||||
|
||||
@Searchable()
|
||||
@Property({ columnType: "text", nullable: true })
|
||||
upc?: string | null
|
||||
|
||||
|
||||
@@ -72,6 +72,7 @@ class Product {
|
||||
@Property({ columnType: "text" })
|
||||
handle?: string
|
||||
|
||||
@Searchable()
|
||||
@Property({ columnType: "text", nullable: true })
|
||||
subtitle?: string | null
|
||||
|
||||
|
||||
@@ -1,7 +1,3 @@
|
||||
export { default as ProductService } from "./product"
|
||||
export { default as ProductCategoryService } from "./product-category"
|
||||
export { default as ProductCollectionService } from "./product-collection"
|
||||
export { default as ProductModuleService } from "./product-module-service"
|
||||
export { default as ProductTagService } from "./product-tag"
|
||||
export { default as ProductTypeService } from "./product-type"
|
||||
export { default as ProductOptionService } from "./product-option"
|
||||
|
||||
@@ -1,63 +0,0 @@
|
||||
import { Context, DAL, FindConfig, ProductTypes } from "@medusajs/types"
|
||||
import { InjectManager, MedusaContext, ModulesSdkUtils } from "@medusajs/utils"
|
||||
|
||||
import { ProductCollection } from "@models"
|
||||
|
||||
type InjectedDependencies = {
|
||||
productCollectionRepository: DAL.RepositoryService
|
||||
}
|
||||
|
||||
export default class ProductCollectionService<
|
||||
TEntity extends ProductCollection = ProductCollection
|
||||
> extends ModulesSdkUtils.internalModuleServiceFactory<InjectedDependencies>(
|
||||
ProductCollection
|
||||
)<TEntity> {
|
||||
// eslint-disable-next-line max-len
|
||||
protected readonly productCollectionRepository_: DAL.RepositoryService<TEntity>
|
||||
|
||||
constructor(container: InjectedDependencies) {
|
||||
super(container)
|
||||
this.productCollectionRepository_ = container.productCollectionRepository
|
||||
}
|
||||
|
||||
@InjectManager("productCollectionRepository_")
|
||||
async list(
|
||||
filters: ProductTypes.FilterableProductCollectionProps = {},
|
||||
config: FindConfig<TEntity> = {},
|
||||
@MedusaContext() sharedContext: Context = {}
|
||||
): Promise<TEntity[]> {
|
||||
return await this.productCollectionRepository_.find(
|
||||
this.buildListQueryOptions(filters, config),
|
||||
sharedContext
|
||||
)
|
||||
}
|
||||
|
||||
@InjectManager("productCollectionRepository_")
|
||||
async listAndCount(
|
||||
filters: ProductTypes.FilterableProductCollectionProps = {},
|
||||
config: FindConfig<TEntity> = {},
|
||||
@MedusaContext() sharedContext: Context = {}
|
||||
): Promise<[TEntity[], number]> {
|
||||
return await this.productCollectionRepository_.findAndCount(
|
||||
this.buildListQueryOptions(filters, config),
|
||||
sharedContext
|
||||
)
|
||||
}
|
||||
|
||||
protected buildListQueryOptions(
|
||||
filters: ProductTypes.FilterableProductCollectionProps = {},
|
||||
config: FindConfig<TEntity> = {}
|
||||
): DAL.FindOptions<TEntity> {
|
||||
const queryOptions = ModulesSdkUtils.buildQuery<TEntity>(filters, config)
|
||||
|
||||
queryOptions.where ??= {}
|
||||
|
||||
if (filters.title) {
|
||||
queryOptions.where.title = {
|
||||
$like: `%${filters.title}%`,
|
||||
} as DAL.FindOptions<TEntity>["where"]["title"]
|
||||
}
|
||||
|
||||
return queryOptions
|
||||
}
|
||||
}
|
||||
@@ -18,14 +18,7 @@ import {
|
||||
ProductType,
|
||||
ProductVariant,
|
||||
} from "@models"
|
||||
import {
|
||||
ProductCategoryService,
|
||||
ProductCollectionService,
|
||||
ProductOptionService,
|
||||
ProductService,
|
||||
ProductTagService,
|
||||
ProductTypeService,
|
||||
} from "@services"
|
||||
import { ProductCategoryService, ProductService } from "@services"
|
||||
|
||||
import {
|
||||
arrayDifference,
|
||||
@@ -58,12 +51,12 @@ type InjectedDependencies = {
|
||||
baseRepository: DAL.RepositoryService
|
||||
productService: ProductService<any>
|
||||
productVariantService: ModulesSdkTypes.InternalModuleService<any, any>
|
||||
productTagService: ProductTagService<any>
|
||||
productTagService: ModulesSdkTypes.InternalModuleService<any>
|
||||
productCategoryService: ProductCategoryService<any>
|
||||
productCollectionService: ProductCollectionService<any>
|
||||
productCollectionService: ModulesSdkTypes.InternalModuleService<any>
|
||||
productImageService: ModulesSdkTypes.InternalModuleService<any>
|
||||
productTypeService: ProductTypeService<any>
|
||||
productOptionService: ProductOptionService<any>
|
||||
productTypeService: ModulesSdkTypes.InternalModuleService<any>
|
||||
productOptionService: ModulesSdkTypes.InternalModuleService<any>
|
||||
productOptionValueService: ModulesSdkTypes.InternalModuleService<any>
|
||||
eventBusModuleService?: IEventBusModuleService
|
||||
}
|
||||
@@ -133,13 +126,13 @@ export default class ProductModuleService<
|
||||
|
||||
// eslint-disable-next-line max-len
|
||||
protected readonly productCategoryService_: ProductCategoryService<TProductCategory>
|
||||
protected readonly productTagService_: ProductTagService<TProductTag>
|
||||
protected readonly productTagService_: ModulesSdkTypes.InternalModuleService<TProductTag>
|
||||
// eslint-disable-next-line max-len
|
||||
protected readonly productCollectionService_: ProductCollectionService<TProductCollection>
|
||||
protected readonly productCollectionService_: ModulesSdkTypes.InternalModuleService<TProductCollection>
|
||||
// eslint-disable-next-line max-len
|
||||
protected readonly productImageService_: ModulesSdkTypes.InternalModuleService<TProductImage>
|
||||
protected readonly productTypeService_: ProductTypeService<TProductType>
|
||||
protected readonly productOptionService_: ProductOptionService<TProductOption>
|
||||
protected readonly productTypeService_: ModulesSdkTypes.InternalModuleService<TProductType>
|
||||
protected readonly productOptionService_: ModulesSdkTypes.InternalModuleService<TProductOption>
|
||||
// eslint-disable-next-line max-len
|
||||
protected readonly productOptionValueService_: ModulesSdkTypes.InternalModuleService<TProductOptionValue>
|
||||
protected readonly eventBusModuleService_?: IEventBusModuleService
|
||||
@@ -1122,8 +1115,8 @@ export default class ProductModuleService<
|
||||
data: ProductTypes.CreateProductDTO[],
|
||||
@MedusaContext() sharedContext: Context = {}
|
||||
): Promise<TProduct[]> {
|
||||
const normalizedInput = data.map(
|
||||
ProductModuleService.normalizeCreateProductInput
|
||||
const normalizedInput = await Promise.all(
|
||||
data.map((d) => this.normalizeCreateProductInput(d, sharedContext))
|
||||
)
|
||||
|
||||
const productData = await this.productService_.upsertWithReplace(
|
||||
@@ -1178,8 +1171,8 @@ export default class ProductModuleService<
|
||||
data: UpdateProductInput[],
|
||||
@MedusaContext() sharedContext: Context = {}
|
||||
): Promise<TProduct[]> {
|
||||
const normalizedInput = data.map(
|
||||
ProductModuleService.normalizeUpdateProductInput
|
||||
const normalizedInput = await Promise.all(
|
||||
data.map((d) => this.normalizeUpdateProductInput(d, sharedContext))
|
||||
)
|
||||
|
||||
const productData = await this.productService_.upsertWithReplace(
|
||||
@@ -1260,12 +1253,13 @@ export default class ProductModuleService<
|
||||
return productData
|
||||
}
|
||||
|
||||
protected static normalizeCreateProductInput(
|
||||
product: ProductTypes.CreateProductDTO
|
||||
): ProductTypes.CreateProductDTO {
|
||||
const productData = ProductModuleService.normalizeUpdateProductInput(
|
||||
protected async normalizeCreateProductInput(
|
||||
product: ProductTypes.CreateProductDTO,
|
||||
@MedusaContext() sharedContext: Context = {}
|
||||
): Promise<ProductTypes.CreateProductDTO> {
|
||||
const productData = (await this.normalizeUpdateProductInput(
|
||||
product as UpdateProductInput
|
||||
) as ProductTypes.CreateProductDTO
|
||||
)) as ProductTypes.CreateProductDTO
|
||||
|
||||
if (!productData.handle && productData.title) {
|
||||
productData.handle = kebabCase(productData.title)
|
||||
@@ -1282,14 +1276,35 @@ export default class ProductModuleService<
|
||||
return productData
|
||||
}
|
||||
|
||||
protected static normalizeUpdateProductInput(
|
||||
product: UpdateProductInput
|
||||
): UpdateProductInput {
|
||||
protected async normalizeUpdateProductInput(
|
||||
product: UpdateProductInput,
|
||||
@MedusaContext() sharedContext: Context = {}
|
||||
): Promise<UpdateProductInput> {
|
||||
const productData = { ...product }
|
||||
if (productData.is_giftcard) {
|
||||
productData.discountable = false
|
||||
}
|
||||
|
||||
if (productData.tags?.length && productData.tags.some((t) => !t.id)) {
|
||||
const dbTags = await this.productTagService_.list(
|
||||
{
|
||||
value: productData.tags
|
||||
.map((t) => t.value)
|
||||
.filter((v) => !!v) as string[],
|
||||
},
|
||||
{ take: null },
|
||||
sharedContext
|
||||
)
|
||||
|
||||
productData.tags = productData.tags.map((tag) => {
|
||||
const dbTag = dbTags.find((t) => t.value === tag.value)
|
||||
return {
|
||||
...tag,
|
||||
...(dbTag ? { id: dbTag.id } : {}),
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
if (productData.options?.length) {
|
||||
;(productData as any).options = productData.options?.map((option) => {
|
||||
return {
|
||||
|
||||
@@ -1,59 +0,0 @@
|
||||
import { ProductOption } from "@models"
|
||||
import { Context, DAL, FindConfig, ProductTypes } from "@medusajs/types"
|
||||
import { InjectManager, MedusaContext, ModulesSdkUtils } from "@medusajs/utils"
|
||||
|
||||
type InjectedDependencies = {
|
||||
productOptionRepository: DAL.RepositoryService
|
||||
}
|
||||
|
||||
export default class ProductOptionService<
|
||||
TEntity extends ProductOption = ProductOption
|
||||
> extends ModulesSdkUtils.internalModuleServiceFactory<InjectedDependencies>(
|
||||
ProductOption
|
||||
)<TEntity> {
|
||||
protected readonly productOptionRepository_: DAL.RepositoryService<TEntity>
|
||||
|
||||
constructor(container: InjectedDependencies) {
|
||||
super(container)
|
||||
this.productOptionRepository_ = container.productOptionRepository
|
||||
}
|
||||
|
||||
@InjectManager("productOptionRepository_")
|
||||
async list(
|
||||
filters: ProductTypes.FilterableProductOptionProps = {},
|
||||
config: FindConfig<TEntity> = {},
|
||||
@MedusaContext() sharedContext?: Context
|
||||
): Promise<TEntity[]> {
|
||||
return await this.productOptionRepository_.find(
|
||||
this.buildQueryForList(filters, config),
|
||||
sharedContext
|
||||
)
|
||||
}
|
||||
|
||||
@InjectManager("productOptionRepository_")
|
||||
async listAndCount(
|
||||
filters: ProductTypes.FilterableProductOptionProps = {},
|
||||
config: FindConfig<TEntity> = {},
|
||||
@MedusaContext() sharedContext?: Context
|
||||
): Promise<[TEntity[], number]> {
|
||||
return await this.productOptionRepository_.findAndCount(
|
||||
this.buildQueryForList(filters, config),
|
||||
sharedContext
|
||||
)
|
||||
}
|
||||
|
||||
private buildQueryForList(
|
||||
filters: ProductTypes.FilterableProductOptionProps = {},
|
||||
config: FindConfig<TEntity> = {}
|
||||
): DAL.FindOptions<TEntity> {
|
||||
const queryOptions = ModulesSdkUtils.buildQuery<TEntity>(filters, config)
|
||||
|
||||
if (filters.title) {
|
||||
queryOptions.where.title = {
|
||||
$ilike: filters.title,
|
||||
} as DAL.FindOptions<TEntity>["where"]["title"]
|
||||
}
|
||||
|
||||
return queryOptions
|
||||
}
|
||||
}
|
||||
@@ -1,58 +0,0 @@
|
||||
import { ProductTag } from "@models"
|
||||
import { Context, DAL, FindConfig, ProductTypes } from "@medusajs/types"
|
||||
import { InjectManager, MedusaContext, ModulesSdkUtils } from "@medusajs/utils"
|
||||
|
||||
type InjectedDependencies = {
|
||||
productTagRepository: DAL.RepositoryService
|
||||
}
|
||||
|
||||
export default class ProductTagService<
|
||||
TEntity extends ProductTag = ProductTag
|
||||
> extends ModulesSdkUtils.internalModuleServiceFactory<InjectedDependencies>(
|
||||
ProductTag
|
||||
)<TEntity> {
|
||||
protected readonly productTagRepository_: DAL.RepositoryService<TEntity>
|
||||
|
||||
constructor(container: InjectedDependencies) {
|
||||
super(container)
|
||||
this.productTagRepository_ = container.productTagRepository
|
||||
}
|
||||
@InjectManager("productTagRepository_")
|
||||
async list(
|
||||
filters: ProductTypes.FilterableProductTagProps = {},
|
||||
config: FindConfig<TEntity> = {},
|
||||
@MedusaContext() sharedContext: Context = {}
|
||||
): Promise<TEntity[]> {
|
||||
return await this.productTagRepository_.find(
|
||||
this.buildQueryForList(filters, config),
|
||||
sharedContext
|
||||
)
|
||||
}
|
||||
|
||||
@InjectManager("productTagRepository_")
|
||||
async listAndCount(
|
||||
filters: ProductTypes.FilterableProductTagProps = {},
|
||||
config: FindConfig<TEntity> = {},
|
||||
@MedusaContext() sharedContext: Context = {}
|
||||
): Promise<[TEntity[], number]> {
|
||||
return await this.productTagRepository_.findAndCount(
|
||||
this.buildQueryForList(filters, config),
|
||||
sharedContext
|
||||
)
|
||||
}
|
||||
|
||||
private buildQueryForList(
|
||||
filters: ProductTypes.FilterableProductTagProps = {},
|
||||
config: FindConfig<TEntity> = {}
|
||||
): DAL.FindOptions<TEntity> {
|
||||
const queryOptions = ModulesSdkUtils.buildQuery<TEntity>(filters, config)
|
||||
|
||||
if (filters.value) {
|
||||
queryOptions.where.value = {
|
||||
$ilike: filters.value,
|
||||
} as DAL.FindOptions<TEntity>["where"]["value"]
|
||||
}
|
||||
|
||||
return queryOptions
|
||||
}
|
||||
}
|
||||
@@ -1,59 +0,0 @@
|
||||
import { ProductType } from "@models"
|
||||
import { Context, DAL, FindConfig, ProductTypes } from "@medusajs/types"
|
||||
import { InjectManager, MedusaContext, ModulesSdkUtils } from "@medusajs/utils"
|
||||
|
||||
type InjectedDependencies = {
|
||||
productTypeRepository: DAL.RepositoryService
|
||||
}
|
||||
|
||||
export default class ProductTypeService<
|
||||
TEntity extends ProductType = ProductType
|
||||
> extends ModulesSdkUtils.internalModuleServiceFactory<InjectedDependencies>(
|
||||
ProductType
|
||||
)<TEntity> {
|
||||
protected readonly productTypeRepository_: DAL.RepositoryService<TEntity>
|
||||
|
||||
constructor(container: InjectedDependencies) {
|
||||
super(container)
|
||||
this.productTypeRepository_ = container.productTypeRepository
|
||||
}
|
||||
|
||||
@InjectManager("productTypeRepository_")
|
||||
async list(
|
||||
filters: ProductTypes.FilterableProductTypeProps = {},
|
||||
config: FindConfig<TEntity> = {},
|
||||
@MedusaContext() sharedContext: Context = {}
|
||||
): Promise<TEntity[]> {
|
||||
return await this.productTypeRepository_.find(
|
||||
this.buildQueryForList(filters, config),
|
||||
sharedContext
|
||||
)
|
||||
}
|
||||
|
||||
@InjectManager("productTypeRepository_")
|
||||
async listAndCount(
|
||||
filters: ProductTypes.FilterableProductTypeProps = {},
|
||||
config: FindConfig<TEntity> = {},
|
||||
@MedusaContext() sharedContext: Context = {}
|
||||
): Promise<[TEntity[], number]> {
|
||||
return await this.productTypeRepository_.findAndCount(
|
||||
this.buildQueryForList(filters, config),
|
||||
sharedContext
|
||||
)
|
||||
}
|
||||
|
||||
private buildQueryForList(
|
||||
filters: ProductTypes.FilterableProductTypeProps = {},
|
||||
config: FindConfig<TEntity> = {}
|
||||
): DAL.FindOptions<TEntity> {
|
||||
const queryOptions = ModulesSdkUtils.buildQuery<TEntity>(filters, config)
|
||||
|
||||
if (filters.value) {
|
||||
queryOptions.where.value = {
|
||||
$ilike: filters.value,
|
||||
} as DAL.FindOptions<TEntity>["where"]["value"]
|
||||
}
|
||||
|
||||
return queryOptions
|
||||
}
|
||||
}
|
||||
@@ -724,6 +724,10 @@ export interface FilterableProductProps
|
||||
*/
|
||||
export interface FilterableProductTagProps
|
||||
extends BaseFilterable<FilterableProductTagProps> {
|
||||
/**
|
||||
* Search through the tags' values.
|
||||
*/
|
||||
q?: string
|
||||
/**
|
||||
* The IDs to filter product tags by.
|
||||
*/
|
||||
@@ -731,7 +735,7 @@ export interface FilterableProductTagProps
|
||||
/**
|
||||
* The value to filter product tags by.
|
||||
*/
|
||||
value?: string
|
||||
value?: string | string[]
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -744,6 +748,10 @@ export interface FilterableProductTagProps
|
||||
*/
|
||||
export interface FilterableProductTypeProps
|
||||
extends BaseFilterable<FilterableProductTypeProps> {
|
||||
/**
|
||||
* Search through the types' values.
|
||||
*/
|
||||
q?: string
|
||||
/**
|
||||
* The IDs to filter product types by.
|
||||
*/
|
||||
@@ -765,6 +773,10 @@ export interface FilterableProductTypeProps
|
||||
*/
|
||||
export interface FilterableProductOptionProps
|
||||
extends BaseFilterable<FilterableProductOptionProps> {
|
||||
/**
|
||||
* Search through the options' titles.
|
||||
*/
|
||||
q?: string
|
||||
/**
|
||||
* The IDs to filter product options by.
|
||||
*/
|
||||
@@ -789,6 +801,10 @@ export interface FilterableProductOptionProps
|
||||
*/
|
||||
export interface FilterableProductCollectionProps
|
||||
extends BaseFilterable<FilterableProductCollectionProps> {
|
||||
/**
|
||||
* Search through the collections' titles.
|
||||
*/
|
||||
q?: string
|
||||
/**
|
||||
* The IDs to filter product collections by.
|
||||
*/
|
||||
@@ -815,6 +831,10 @@ export interface FilterableProductCollectionProps
|
||||
*/
|
||||
export interface FilterableProductVariantProps
|
||||
extends BaseFilterable<FilterableProductVariantProps> {
|
||||
/**
|
||||
* Search through the title and different code attributes on the variant
|
||||
*/
|
||||
q?: string
|
||||
/**
|
||||
* The IDs to filter product variants by.
|
||||
*/
|
||||
|
||||
@@ -24,7 +24,7 @@ export const dbErrorMapper = (err: Error) => {
|
||||
|
||||
throw new MedusaError(
|
||||
MedusaError.Types.INVALID_DATA,
|
||||
`${upperCaseFirst(info.table)} with ${info.keys
|
||||
`${upperCaseFirst(info.table.split("_").join(" "))} with ${info.keys
|
||||
.map((key, i) => `${key}: ${info.values[i]}`)
|
||||
.join(", ")} already exists.`
|
||||
)
|
||||
@@ -37,7 +37,7 @@ export const dbErrorMapper = (err: Error) => {
|
||||
throw new MedusaError(
|
||||
MedusaError.Types.INVALID_DATA,
|
||||
`Cannot set field '${(err as any).column}' of ${upperCaseFirst(
|
||||
(err as any).table
|
||||
(err as any).table.split("_").join(" ")
|
||||
)} to null`
|
||||
)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user