add: abstract search functionality to core + adjust meilisearch-plugin

add SearchService interface to medusa-interfaces
add DefaultSearchService skeleton implementation to core
add search-index.js loader to core for indexing db documents
add SearchSubscriber to core
add loadToSearchEngine method in ProductService
switch order of loaders in core to load subscriptions AFTER plugins
adjust service and loader for medusa-plugin-meilisearch
This commit is contained in:
zakariaelas
2021-09-16 15:48:09 +01:00
parent 425c8a5e5d
commit 0fbde7c51f
24 changed files with 405 additions and 187 deletions
@@ -1,10 +0,0 @@
import { Router } from "express"
import routes from "./routes/"
export default (container) => {
const app = Router()
routes(app)
return app
}
@@ -1 +0,0 @@
export default (fn) => (...args) => fn(...args).catch(args[2])
@@ -1,5 +0,0 @@
import { default as wrap } from "./await-middleware"
export default {
wrap,
}
@@ -1,17 +0,0 @@
import { Router } from "express"
import bodyParser from "body-parser"
import middlewares from "../middlewares"
const route = Router()
export default (app) => {
app.use("/meilisearch", route)
route.post(
"/search",
bodyParser.json(),
middlewares.wrap(require("./meilisearch").default)
)
return app
}
@@ -1,26 +0,0 @@
import { Validator, MedusaError } from "medusa-core-utils"
export default async (req, res) => {
const schema = Validator.object()
.keys({
q: Validator.string().required(),
})
.options({ allowUnknown: true })
const { value, error } = schema.validate(req.body)
if (error) {
throw new MedusaError(MedusaError.Types.INVALID_DATA, error.details)
}
try {
const { q, ...options } = value
const meiliSearchService = req.scope.resolve("meilisearchService")
const results = await meiliSearchService.search(q, options)
res.status(200).send(results)
} catch (error) {
throw error
}
}
@@ -1,25 +1,14 @@
import {
defaultProductFields,
defaultProductRelations,
flattenSkus,
} from "../utils"
const INDEX_NS = "medusa-commerce"
export default async (container, options) => {
try {
const meilisearchService = container.resolve("meilisearchService")
const productService = container.resolve("productService")
const searchService = container.resolve("searchService")
const products = await productService.list(
{},
{
select: defaultProductFields,
relations: defaultProductRelations,
}
await Promise.all(
Object.entries(options.settings).map(([key, value]) =>
searchService.updateSettings(`${INDEX_NS}_${key}`, value)
)
)
const productsWithSkus = products.map((product) => flattenSkus(product))
await meilisearchService.updateSettings()
await meilisearchService.addDocuments(productsWithSkus)
} catch (err) {
console.log(err)
}
@@ -1,36 +1,46 @@
import { BaseService } from "medusa-interfaces"
import { SearchService } from "medusa-interfaces"
import { MeiliSearch } from "meilisearch"
class MeilisearchService extends BaseService {
constructor({ eventBusService }, options) {
class MeiliSearchService extends SearchService {
constructor(container, options) {
super()
this.eventBus_ = eventBusService
this.options_ = options
this.client_ = new MeiliSearch(options.config).index("products")
this.client_ = new MeiliSearch(options.config)
}
deleteAllDocuments() {
return this.client_.deleteAllDocuments()
createIndex(indexName, options) {
return this.client_.createIndex(indexName, options)
}
addDocuments(documents) {
return this.client_.addDocuments(documents)
getIndex(indexName) {
return this.client_.index(indexName)
}
deleteDocument(document_id) {
return this.client_.deleteDocument(document_id)
addDocuments(indexName, documents) {
return this.client_.index(indexName).addDocuments(documents)
}
search(query, options) {
return this.client_.search(query, options)
replaceDocuments(indexName, documents) {
return this.client_.index(indexName).addDocuments(documents)
}
updateSettings() {
return this.client_.updateSettings(this.options_.settings)
deleteDocument(indexName, document_id) {
return this.client_.index(indexName).deleteDocument(document_id)
}
deleteAllDocuments(indexName) {
return this.client_.index(indexName).deleteAllDocuments()
}
search(indexName, query, options) {
return this.client_.index(indexName).search(query, options)
}
updateSettings(indexName, settings) {
return this.client_.index(indexName).updateSettings(settings)
}
}
export default MeilisearchService
export default MeiliSearchService
@@ -1,69 +0,0 @@
import {
defaultProductRelations,
defaultProductFields,
flattenSkus,
} from "../utils"
class MeilisearchSubscriber {
constructor(
{ eventBusService, meilisearchService, productService },
options
) {
this.eventBus_ = eventBusService
this.meilisearchService_ = meilisearchService
this.productService_ = productService
this.eventBus_.subscribe("product.created", this.handleProductCreation)
this.eventBus_.subscribe("product.updated", this.handleProductUpdate)
this.eventBus_.subscribe("product.deleted", this.handleProductDeletion)
this.eventBus_.subscribe(
"product-variant.created",
this.handleProductVariantChange
)
this.eventBus_.subscribe(
"product-variant.updated",
this.handleProductVariantChange
)
this.eventBus_.subscribe(
"product-variant.deleted",
this.handleProductVariantChange
)
}
handleProductCreation = async (data) => {
const product = await this.retrieveProduct_(data.id)
await this.meilisearchService_.addDocuments([product])
}
retrieveProduct_ = async (product_id) => {
const product = await this.productService_.retrieve(product_id, {
relations: defaultProductRelations,
select: defaultProductFields,
})
const flattenedProduct = flattenSkus(product)
return flattenedProduct
}
handleProductUpdate = async (data) => {
const product = await this.retrieveProduct_(data.id)
await this.meilisearchService_.addDocuments([product])
}
handleProductDeletion = async (data) => {
await this.meilisearchService_.deleteDocument(data.id)
}
handleProductVariantChange = async (data) => {
const product = await this.retrieveProduct_(data.product_id)
await this.meilisearchService_.addDocuments([product])
}
}
export default MeilisearchSubscriber
@@ -1,29 +0,0 @@
export const flattenSkus = (product) => {
const skus = product.variants.map((v) => v.sku).filter(Boolean)
product.sku = skus
return product
}
export const defaultProductFields = [
"id",
"title",
"subtitle",
"description",
"handle",
"is_giftcard",
"discountable",
"thumbnail",
"profile_id",
"collection_id",
"type_id",
"origin_country",
"created_at",
"updated_at",
]
export const defaultProductRelations = [
"variants",
"tags",
"type",
"collection",
]