add: medusa-plugin-meilisearch + add deleted event in productService + tweak emitted event data in productVariantService
This commit is contained in:
13
packages/medusa-plugin-meilisearch/.babelrc
Normal file
13
packages/medusa-plugin-meilisearch/.babelrc
Normal file
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"plugins": [
|
||||
"@babel/plugin-proposal-class-properties",
|
||||
"@babel/plugin-transform-instanceof",
|
||||
"@babel/plugin-transform-classes"
|
||||
],
|
||||
"presets": ["@babel/preset-env"],
|
||||
"env": {
|
||||
"test": {
|
||||
"plugins": ["@babel/plugin-transform-runtime"]
|
||||
}
|
||||
}
|
||||
}
|
||||
9
packages/medusa-plugin-meilisearch/.eslintrc
Normal file
9
packages/medusa-plugin-meilisearch/.eslintrc
Normal file
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"plugins": ["prettier"],
|
||||
"extends": ["prettier"],
|
||||
"rules": {
|
||||
"prettier/prettier": "error",
|
||||
"semi": "error",
|
||||
"no-unused-expressions": "true"
|
||||
}
|
||||
}
|
||||
15
packages/medusa-plugin-meilisearch/.gitignore
vendored
Normal file
15
packages/medusa-plugin-meilisearch/.gitignore
vendored
Normal file
@@ -0,0 +1,15 @@
|
||||
/dist
|
||||
.env
|
||||
.DS_Store
|
||||
/uploads
|
||||
/node_modules
|
||||
yarn-error.log
|
||||
|
||||
/dist
|
||||
|
||||
/api
|
||||
/services
|
||||
/models
|
||||
/subscribers
|
||||
/loaders
|
||||
/utils
|
||||
13
packages/medusa-plugin-meilisearch/.npmignore
Normal file
13
packages/medusa-plugin-meilisearch/.npmignore
Normal file
@@ -0,0 +1,13 @@
|
||||
/lib
|
||||
node_modules
|
||||
.DS_store
|
||||
.env*
|
||||
/*.js
|
||||
!index.js
|
||||
yarn.lock
|
||||
src
|
||||
.gitignore
|
||||
.eslintrc
|
||||
.babelrc
|
||||
.prettierrc
|
||||
|
||||
7
packages/medusa-plugin-meilisearch/.prettierrc
Normal file
7
packages/medusa-plugin-meilisearch/.prettierrc
Normal file
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"endOfLine": "lf",
|
||||
"semi": false,
|
||||
"singleQuote": false,
|
||||
"tabWidth": 2,
|
||||
"trailingComma": "es5"
|
||||
}
|
||||
19
packages/medusa-plugin-meilisearch/README.md
Normal file
19
packages/medusa-plugin-meilisearch/README.md
Normal file
@@ -0,0 +1,19 @@
|
||||
# medusa-plugin-meilisearch
|
||||
|
||||
Meilisearch Plugin for Medusa to search for products.
|
||||
|
||||
## Plugin Options
|
||||
```
|
||||
{
|
||||
config: {
|
||||
host: [your meilisearch host],
|
||||
},
|
||||
settings: [meilisearch settings passed to meilisearch's `updateSettings()` method on an index:
|
||||
//example
|
||||
{
|
||||
searchableAttributes: ["title", "description", "sku"],
|
||||
displayedAttributes: ["title", "description", "sku"],
|
||||
}
|
||||
],
|
||||
}
|
||||
```
|
||||
1
packages/medusa-plugin-meilisearch/index.js
Normal file
1
packages/medusa-plugin-meilisearch/index.js
Normal file
@@ -0,0 +1 @@
|
||||
//noop
|
||||
3
packages/medusa-plugin-meilisearch/jest.config.js
Normal file
3
packages/medusa-plugin-meilisearch/jest.config.js
Normal file
@@ -0,0 +1,3 @@
|
||||
module.exports = {
|
||||
testEnvironment: "node",
|
||||
}
|
||||
8205
packages/medusa-plugin-meilisearch/package-lock.json
generated
Normal file
8205
packages/medusa-plugin-meilisearch/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
45
packages/medusa-plugin-meilisearch/package.json
Normal file
45
packages/medusa-plugin-meilisearch/package.json
Normal file
@@ -0,0 +1,45 @@
|
||||
{
|
||||
"name": "medusa-plugin-meilisearch",
|
||||
"version": "0.0.1",
|
||||
"description": "A starter for Medusa projects.",
|
||||
"main": "index.js",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/medusajs/medusa",
|
||||
"directory": "packages/medusa-plugin-meilisearch"
|
||||
},
|
||||
"author": "Zakaria El Asri",
|
||||
"license": "MIT",
|
||||
"scripts": {
|
||||
"build": "babel src -d .",
|
||||
"prepare": "cross-env NODE_ENV=production npm run build",
|
||||
"watch": "babel -w src --out-dir . --ignore **/__tests__",
|
||||
"test": "jest"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"medusa-interfaces": "1.x",
|
||||
"typeorm": "0.x"
|
||||
},
|
||||
"dependencies": {
|
||||
"body-parser": "^1.19.0",
|
||||
"lodash": "^4.17.21",
|
||||
"medusa-core-utils": "^1.1.20",
|
||||
"medusa-interfaces": "^1.1.21",
|
||||
"meilisearch": "^0.20.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/cli": "^7.7.5",
|
||||
"@babel/core": "^7.7.5",
|
||||
"@babel/node": "^7.7.4",
|
||||
"@babel/plugin-proposal-class-properties": "^7.7.4",
|
||||
"@babel/plugin-transform-instanceof": "^7.8.3",
|
||||
"@babel/plugin-transform-runtime": "^7.7.6",
|
||||
"@babel/preset-env": "^7.7.5",
|
||||
"@babel/register": "^7.7.4",
|
||||
"@babel/runtime": "^7.9.6",
|
||||
"client-sessions": "^0.8.0",
|
||||
"cross-env": "^5.2.1",
|
||||
"eslint": "^6.8.0",
|
||||
"jest": "^25.5.2"
|
||||
}
|
||||
}
|
||||
10
packages/medusa-plugin-meilisearch/src/api/index.js
Normal file
10
packages/medusa-plugin-meilisearch/src/api/index.js
Normal file
@@ -0,0 +1,10 @@
|
||||
import { Router } from "express"
|
||||
import routes from "./routes/"
|
||||
|
||||
export default (container) => {
|
||||
const app = Router()
|
||||
|
||||
routes(app)
|
||||
|
||||
return app
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
export default (fn) => (...args) => fn(...args).catch(args[2])
|
||||
@@ -0,0 +1,5 @@
|
||||
import { default as wrap } from "./await-middleware"
|
||||
|
||||
export default {
|
||||
wrap,
|
||||
}
|
||||
17
packages/medusa-plugin-meilisearch/src/api/routes/index.js
Normal file
17
packages/medusa-plugin-meilisearch/src/api/routes/index.js
Normal file
@@ -0,0 +1,17 @@
|
||||
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
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
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) {
|
||||
console.log({ 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
|
||||
}
|
||||
}
|
||||
26
packages/medusa-plugin-meilisearch/src/loaders/index.js
Normal file
26
packages/medusa-plugin-meilisearch/src/loaders/index.js
Normal file
@@ -0,0 +1,26 @@
|
||||
import {
|
||||
defaultProductFields,
|
||||
defaultProductRelations,
|
||||
flattenSkus,
|
||||
} from "../utils"
|
||||
|
||||
export default async (container, options) => {
|
||||
try {
|
||||
const meilisearchService = container.resolve("meilisearchService")
|
||||
const productService = container.resolve("productService")
|
||||
|
||||
const products = await productService.list(
|
||||
{},
|
||||
{
|
||||
select: defaultProductFields,
|
||||
relations: defaultProductRelations,
|
||||
}
|
||||
)
|
||||
const productsWithSkus = products.map((product) => flattenSkus(product))
|
||||
|
||||
await meilisearchService.updateSettings()
|
||||
await meilisearchService.addDocuments(productsWithSkus)
|
||||
} catch (err) {
|
||||
console.log(err)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
import { BaseService } from "medusa-interfaces"
|
||||
import { MeiliSearch } from "meilisearch"
|
||||
|
||||
class MeilisearchService extends BaseService {
|
||||
constructor({ eventBusService }, options) {
|
||||
super()
|
||||
|
||||
this.eventBus_ = eventBusService
|
||||
|
||||
this.options_ = options
|
||||
|
||||
this.client_ = new MeiliSearch(options.config).index("products")
|
||||
}
|
||||
|
||||
deleteAllDocuments() {
|
||||
return this.client_.deleteAllDocuments()
|
||||
}
|
||||
|
||||
addDocuments(documents) {
|
||||
return this.client_.addDocuments(documents)
|
||||
}
|
||||
|
||||
deleteDocument(document_id) {
|
||||
return this.client_.deleteDocument(document_id)
|
||||
}
|
||||
|
||||
search(query, options) {
|
||||
return this.client_.search(query, options)
|
||||
}
|
||||
|
||||
updateSettings() {
|
||||
return this.client_.updateSettings(this.options_.settings)
|
||||
}
|
||||
}
|
||||
|
||||
export default MeilisearchService
|
||||
@@ -0,0 +1,71 @@
|
||||
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) => {
|
||||
console.log({ change: data })
|
||||
const product = await this.retrieveProduct_(data.product_id)
|
||||
console.log(product.variants)
|
||||
await this.meilisearchService_.addDocuments([product])
|
||||
}
|
||||
}
|
||||
|
||||
export default MeilisearchSubscriber
|
||||
29
packages/medusa-plugin-meilisearch/src/utils/index.js
Normal file
29
packages/medusa-plugin-meilisearch/src/utils/index.js
Normal file
@@ -0,0 +1,29 @@
|
||||
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",
|
||||
]
|
||||
5453
packages/medusa-plugin-meilisearch/yarn.lock
Normal file
5453
packages/medusa-plugin-meilisearch/yarn.lock
Normal file
File diff suppressed because it is too large
Load Diff
@@ -801,6 +801,7 @@ describe("ProductVariantService", () => {
|
||||
}
|
||||
return Promise.resolve({
|
||||
id: IdMap.getId("ironman"),
|
||||
product_id: IdMap.getId("product-test"),
|
||||
})
|
||||
},
|
||||
})
|
||||
@@ -819,9 +820,20 @@ describe("ProductVariantService", () => {
|
||||
await productVariantService.delete(IdMap.getId("ironman"))
|
||||
|
||||
expect(productVariantRepository.softRemove).toBeCalledTimes(1)
|
||||
expect(productVariantRepository.softRemove).toBeCalledWith({
|
||||
expect(productVariantRepository.softRemove).toBeCalledWith(
|
||||
expect.objectContaining({
|
||||
id: IdMap.getId("ironman"),
|
||||
})
|
||||
)
|
||||
|
||||
expect(eventBusService.emit).toHaveBeenCalledTimes(1)
|
||||
expect(eventBusService.emit).toHaveBeenCalledWith(
|
||||
"product-variant.deleted",
|
||||
{
|
||||
id: IdMap.getId("ironman"),
|
||||
product_id: IdMap.getId("product-test"),
|
||||
}
|
||||
)
|
||||
})
|
||||
|
||||
it("successfully resolves if variant does not exist", async () => {
|
||||
|
||||
@@ -306,6 +306,7 @@ describe("ProductService", () => {
|
||||
const productService = new ProductService({
|
||||
manager: MockManager,
|
||||
productRepository,
|
||||
eventBusService,
|
||||
})
|
||||
|
||||
beforeEach(() => {
|
||||
@@ -318,6 +319,11 @@ describe("ProductService", () => {
|
||||
expect(productRepository.softRemove).toBeCalledWith({
|
||||
id: IdMap.getId("ironman"),
|
||||
})
|
||||
|
||||
expect(eventBusService.emit).toBeCalledTimes(1)
|
||||
expect(eventBusService.emit).toBeCalledWith("product.deleted", {
|
||||
id: IdMap.getId("ironman"),
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
@@ -11,6 +11,7 @@ class ProductVariantService extends BaseService {
|
||||
static Events = {
|
||||
UPDATED: "product-variant.updated",
|
||||
CREATED: "product-variant.created",
|
||||
DELETED: "product-variant.deleted",
|
||||
}
|
||||
|
||||
/** @param { productVariantModel: (ProductVariantModel) } */
|
||||
@@ -229,6 +230,7 @@ class ProductVariantService extends BaseService {
|
||||
.withTransaction(manager)
|
||||
.emit(ProductVariantService.Events.UPDATED, {
|
||||
id: result.id,
|
||||
product_id: result.product_id,
|
||||
})
|
||||
|
||||
return result
|
||||
@@ -305,6 +307,7 @@ class ProductVariantService extends BaseService {
|
||||
.withTransaction(manager)
|
||||
.emit(ProductVariantService.Events.UPDATED, {
|
||||
id: result.id,
|
||||
product_id: result.product_id,
|
||||
fields: Object.keys(update),
|
||||
})
|
||||
return result
|
||||
@@ -587,6 +590,11 @@ class ProductVariantService extends BaseService {
|
||||
|
||||
await variantRepo.softRemove(variant)
|
||||
|
||||
await this.eventBus_.emit(ProductVariantService.Events.DELETED, {
|
||||
id: variant.id,
|
||||
product_id: variant.product_id,
|
||||
})
|
||||
|
||||
return Promise.resolve()
|
||||
})
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@ class ProductService extends BaseService {
|
||||
static Events = {
|
||||
UPDATED: "product.updated",
|
||||
CREATED: "product.created",
|
||||
DELETED: "product.deleted",
|
||||
}
|
||||
|
||||
constructor({
|
||||
@@ -473,6 +474,12 @@ class ProductService extends BaseService {
|
||||
|
||||
await productRepo.softRemove(product)
|
||||
|
||||
await this.eventBus_
|
||||
.withTransaction(manager)
|
||||
.emit(ProductService.Events.DELETED, {
|
||||
id: productId,
|
||||
})
|
||||
|
||||
return Promise.resolve()
|
||||
})
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user