feat(utils): Support free text configuration (#6942)

**What**
- Add support for searchable entity properties to configure default free text search when providing a `q` filter
- `@Searchable` decorator to mark a property or relation to be searchable form the current entity

FIXES CORE-1910
This commit is contained in:
Adrien de Peretti authored and GitHub committed 2024-04-05 13:59:40 +00:00
1 parent a0005faa14
commit 21990fcd4b
19 files changed
+424 -205

No files matched your search

@@ -234,6 +234,31 @@ moduleIntegrationTestRunner({
})
describe("list", () => {
it("should list all product that match the free text search", async () => {
const data = buildProductOnlyData({
title: "test product",
})
const data2 = buildProductOnlyData({
title: "space X",
})
const products = await service.create([data, data2])
const result = await service.list({
q: "test",
})
expect(result).toHaveLength(1)
expect(result[0].title).toEqual("test product")
const result2 = await service.list({
q: "space",
})
expect(result2).toHaveLength(1)
expect(result2[0].title).toEqual("space X")
})
describe("soft deleted", function () {
let product
@@ -4,17 +4,18 @@ import {
Entity,
Filter,
Index,
OnInit,
OneToMany,
OnInit,
PrimaryKey,
Property,
} from "@mikro-orm/core"
import {
DALUtils,
createPsqlIndexStatementHelper,
DALUtils,
generateEntityId,
kebabCase,
Searchable,
} from "@medusajs/utils"
import Product from "./product"
@@ -34,6 +35,7 @@ class ProductCollection {
@PrimaryKey({ columnType: "text" })
id!: string
@Searchable()
@Property({ columnType: "text" })
title: string
@@ -1,8 +1,9 @@
import {
DALUtils,
createPsqlIndexStatementHelper,
DALUtils,
generateEntityId,
optionalNumericSerializer,
Searchable,
} from "@medusajs/utils"
import {
BeforeCreate,
@@ -12,8 +13,8 @@ import {
Filter,
Index,
ManyToOne,
OnInit,
OneToMany,
OnInit,
PrimaryKey,
Property,
} from "@mikro-orm/core"
@@ -76,9 +77,11 @@ class ProductVariant {
@PrimaryKey({ columnType: "text" })
id!: string
@Searchable()
@Property({ columnType: "text" })
title: string
@Searchable()
@Property({ columnType: "text", nullable: true })
sku?: string | null
+9 -1
View File
@@ -19,6 +19,7 @@ import {
generateEntityId,
kebabCase,
ProductUtils,
Searchable,
} from "@medusajs/utils"
import ProductCategory from "./product-category"
import ProductCollection from "./product-collection"
@@ -64,6 +65,7 @@ class Product {
@PrimaryKey({ columnType: "text" })
id!: string
@Searchable()
@Property({ columnType: "text" })
title: string
@@ -73,7 +75,11 @@ class Product {
@Property({ columnType: "text", nullable: true })
subtitle?: string | null
@Property({ columnType: "text", nullable: true })
@Searchable()
@Property({
columnType: "text",
nullable: true,
})
description?: string | null
@Property({ columnType: "boolean", default: false })
@@ -91,6 +97,7 @@ class Product {
})
options = new Collection<ProductOption>(this)
@Searchable()
@OneToMany(() => ProductVariant, (variant) => variant.product, {
cascade: ["soft-remove"] as any,
})
@@ -120,6 +127,7 @@ class Product {
@Property({ columnType: "text", nullable: true })
material?: string | null
@Searchable()
@ManyToOne(() => ProductCollection, {
columnType: "text",
nullable: true,
@@ -15,40 +15,6 @@ export class ProductRepository extends DALUtils.mikroOrmBaseRepositoryFactory<Pr
super(...arguments)
}
async find(
findOptions: DAL.FindOptions<Product & { q?: string }> = { where: {} },
context: Context = {}
): Promise<Product[]> {
const findOptions_ = { ...findOptions }
findOptions_.options ??= {}
await this.mutateNotInCategoriesConstraints(findOptions_)
this.applyFreeTextSearchFilters<Product>(
findOptions_,
this.getFreeTextSearchConstraints
)
return await super.find(findOptions_, context)
}
async findAndCount(
findOptions: DAL.FindOptions<Product & { q?: string }> = { where: {} },
context: Context = {}
): Promise<[Product[], number]> {
const findOptions_ = { ...findOptions }
findOptions_.options ??= {}
await this.mutateNotInCategoriesConstraints(findOptions_)
this.applyFreeTextSearchFilters<Product>(
findOptions_,
this.getFreeTextSearchConstraints
)
return await super.findAndCount(findOptions_, context)
}
/**
* In order to be able to have a strict not in categories, and prevent a product
* to be return in the case it also belongs to other categories, we need to
@@ -88,42 +54,4 @@ export class ProductRepository extends DALUtils.mikroOrmBaseRepositoryFactory<Pr
}
}
}
protected getFreeTextSearchConstraints(q: string) {
return [
{
description: {
$ilike: `%${q}%`,
},
},
{
title: {
$ilike: `%${q}%`,
},
},
{
collection: {
title: {
$ilike: `%${q}%`,
},
},
},
{
variants: {
$or: [
{
title: {
$ilike: `%${q}%`,
},
},
{
sku: {
$ilike: `%${q}%`,
},
},
],
},
},
]
}
}