Merge pull request #228 from medusajs/plugin/restock-notification

Plugin/restock notification
This commit is contained in:
Sebastian Rindom
2021-04-07 12:32:38 +02:00
committed by GitHub
33 changed files with 5496 additions and 932 deletions

File diff suppressed because it is too large Load Diff

View File

@@ -14,6 +14,7 @@
"@babel/cli": "^7.7.5",
"@babel/core": "^7.7.5",
"@babel/plugin-proposal-class-properties": "^7.7.4",
"@babel/plugin-transform-instanceof": "^7.12.13",
"@babel/plugin-transform-runtime": "^7.7.6",
"@babel/preset-env": "^7.7.5",
"@babel/runtime": "^7.9.6",

View File

@@ -0,0 +1,12 @@
let ignore = [`**/dist`]
// Jest needs to compile this code, but generally we don't want this copied
// to output folders
if (process.env.NODE_ENV !== `test`) {
ignore.push(`**/__tests__`)
}
module.exports = {
presets: [["babel-preset-medusa-package"], ["@babel/preset-typescript"]],
ignore,
}

View File

@@ -0,0 +1,9 @@
{
"plugins": ["prettier"],
"extends": ["prettier"],
"rules": {
"prettier/prettier": "error",
"semi": "error",
"no-unused-expressions": "true"
}
}

View File

@@ -0,0 +1,18 @@
/lib
node_modules
.DS_store
.env*
/*.js
!.babelrc*
!index.js
!jest.config.js
/dist
/api
/services
/models
/subscribers
/migrations
/repositories

View File

@@ -0,0 +1,9 @@
/lib
node_modules
.DS_store
.env*
/*.js
!index.js
yarn.lock

View File

@@ -0,0 +1,7 @@
{
"endOfLine": "lf",
"semi": false,
"singleQuote": false,
"tabWidth": 2,
"trailingComma": "es5"
}

View File

@@ -0,0 +1,89 @@
# medusa-plugin-restock-notification
## Usage
Install the plugin:
`$ yarn add medusa-plugin-restock-notification`
```js
// medusa-config.js
module.exports = {
...,
plugins: [
...,
`medusa-plugin-restock-notification`
]
}
```
The plugin will migrate your database to include the RestockNotification entity, which consists of a variant id of a sold out item and a jsonb list of arrays that wish to be notified about restocks for the item.
## API endpoint
The plugin exposes an endpoint to sign emails up for restock notifications:
```
POST /restock-notifications/variants/:variant_id
Body
{
"email": "seb@test.com"
}
```
The endpoint responds with `200 OK` on succesful signups. If a signup for an already in stock item is attempted the endpoint will have a 400 response code.
## Restock events
The plugin listens for the `product-variant.updated` call and emits a `restock-notification.restocked` event when a variant with restock signups become available.
The data sent with the `restock-notification.restocked` event is:
```
variant_id: The id of the variant to listen for restock events for.
emails: An array of emails that are to be notified of restocks.
e.g.
{
"variant_id": "variant_1234567890",
"emails": ["seb@test.com", "oli@test.com"]
}
```
*Note: This plugin does not send any communication to the customer, communication logic must be implemented or provided through a communication plugin.*
You may use `medusa-plugin-sendgrid` to orchestrate transactional emails.
## Usage with medusa-plugin-sendgrid
Install the plugins:
`$ yarn add medusa-plugin-restock-notification medusa-plugin-sendgrid`
```js
// medusa-config.js
module.exports = {
...,
plugins: [
...,
`medusa-plugin-restock-notification`,
{
resolve: `medusa-plugin-sendgrid`,
options: {
from: SENDGRID_FROM,
api_key: SENDGRID_API_KEY,
medusa_restock_template: `d-13141234123412342314`
}
}
]
}
```
You should set up a dynamic template in SendGrid which will be send for each of the emails in the restock notification.

View File

@@ -0,0 +1 @@
// noop

View File

@@ -0,0 +1,3 @@
module.exports = {
testEnvironment: "node",
}

View File

@@ -0,0 +1,41 @@
{
"name": "medusa-plugin-restock-notification",
"version": "0.0.1",
"main": "index.js",
"repository": {
"type": "git",
"url": "https://github.com/medusajs/medusa",
"directory": "packages/medusa-plugin-restock-notification"
},
"author": "Sebastian Rindom <seb@medusa-commerce.com>",
"license": "MIT",
"devDependencies": {
"@babel/cli": "^7.7.5",
"@babel/core": "^7.7.5",
"@babel/plugin-transform-typescript": "^7.13.0",
"@babel/preset-typescript": "^7.12.7",
"babel-preset-medusa-package": "^1.1.0",
"cross-env": "^5.2.1",
"eslint": "^6.8.0",
"jest": "^25.5.2",
"medusa-test-utils": "^1.1.6",
"pg": "^8.5.1",
"ulid": "^2.3.0"
},
"scripts": {
"build": "babel src -d . --ignore **/__tests__ --extensions \".ts,.js\"",
"prepare": "cross-env NODE_ENV=production npm run build",
"watch": "babel -w src --out-dir . --ignore **/__tests__",
"test": "jest"
},
"peerDependencies": {
"medusa-interfaces": "1.x"
},
"dependencies": {
"@medusajs/medusa": "^1.1.17",
"body-parser": "^1.19.0",
"express": "^4.17.1",
"medusa-core-utils": "^1.1.3"
},
"gitHead": "0646bd395a6056657cb0aa93c13699c4a9dbbcdd"
}

View File

@@ -0,0 +1,10 @@
import { Router } from "express"
import routes from "./routes"
export default (container) => {
const app = Router()
routes(app)
return app
}

View File

@@ -0,0 +1 @@
export default (fn) => (...args) => fn(...args).catch(args[2])

View File

@@ -0,0 +1,5 @@
import { default as wrap } from "./await-middleware"
export default {
wrap,
}

View File

@@ -0,0 +1,25 @@
import { Validator, MedusaError } from "medusa-core-utils"
export default async (req, res) => {
const { variant_id } = req.params
const schema = Validator.object().keys({
email: Validator.string().required(),
})
const { value, error } = schema.validate(req.body)
if (error) {
res.status(400).json({ message: error.message })
return
}
try {
const restockNotificationService = req.scope.resolve(
"restockNotificationService"
)
await restockNotificationService.addEmail(variant_id, value.email)
res.sendStatus(201)
} catch (err) {
res.status(400).json({ message: err.message })
}
}

View File

@@ -0,0 +1,16 @@
import { Router } from "express"
import bodyParser from "body-parser"
import middlewares from "../middleware"
const route = Router()
export default (app) => {
app.use("/restock-notifications", route)
route.post(
"/variants/:variant_id",
bodyParser.json(),
middlewares.wrap(require("./add-email").default)
)
return app
}

View File

@@ -0,0 +1,21 @@
import { MigrationInterface, QueryRunner } from "typeorm"
export class restockNotification1617703530229 implements MigrationInterface {
name = "restockNotification1617703530229"
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(
`CREATE TABLE "restock_notification" ("variant_id" character varying NOT NULL, "emails" jsonb NOT NULL, "created_at" TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now(), "updated_at" TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now(), CONSTRAINT "PK_49181ca04caac807fcec321705a" PRIMARY KEY ("variant_id"))`
)
await queryRunner.query(
`ALTER TABLE "restock_notification" ADD CONSTRAINT "FK_49181ca04caac807fcec321705a" FOREIGN KEY ("variant_id") REFERENCES "product_variant"("id") ON DELETE NO ACTION ON UPDATE NO ACTION`
)
}
public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(
`ALTER TABLE "restock_notification" DROP CONSTRAINT "FK_49181ca04caac807fcec321705a"`
)
await queryRunner.query(`DROP TABLE "restock_notification"`)
}
}

View File

@@ -0,0 +1,54 @@
import {
Entity,
Index,
BeforeInsert,
Column,
DeleteDateColumn,
CreateDateColumn,
UpdateDateColumn,
PrimaryColumn,
ManyToOne,
JoinColumn,
} from "typeorm"
import { ProductVariant } from "@medusajs/medusa"
@Entity()
export class RestockNotification {
@PrimaryColumn()
variant_id: string
@ManyToOne(() => ProductVariant)
@JoinColumn({ name: "variant_id" })
variant: ProductVariant
@Column({ type: "jsonb" })
emails: string[]
@CreateDateColumn({ type: "timestamptz" })
created_at: Date
@UpdateDateColumn({ type: "timestamptz" })
updated_at: Date
}
/**
* @schema restock_notification
* title: "Restock Notification"
* description: "Holds a list of emails that wish to be notifed when an item is restocked."
* x-resourceId: restock_notification
* properties:
* variant_id:
* type: string
* description: "The id of the variant that customers have signed up to be notified about,"
* emails:
* type: string[]
* description: "The emails of customers who wish to be notified about restocks."
* created_at:
* type: string
* format: date-time
* description: "The date time at which the first restock signup was made."
* updated_at:
* type: string
* format: date-time
* description: "The date time at which the first last signup was made."
*/

View File

@@ -0,0 +1,166 @@
import { MockManager, MockRepository } from "medusa-test-utils"
import RestockNotificationService from "../restock-notification"
describe("RestockNotificationService", () => {
const RestockNotificationModel = MockRepository({
findOne: (q) => {
if (q.where.variant_id === "variant_1234") {
return Promise.resolve({
variant_id: "variant_1234",
emails: ["test@tesmail.com"],
})
}
if (q.where.variant_id === "variant_outofstock") {
return Promise.resolve({
variant_id: "variant_outofstock",
emails: ["test@tesmail.com"],
})
}
return Promise.resolve()
},
})
const ProductVariantService = {
retrieve: (id) => {
if (id === "variant_instock") {
return {
id,
inventory_quantity: 10,
}
}
if (id === "variant_1234") {
return {
id,
inventory_quantity: 10,
}
}
return {
id,
inventory_quantity: 0,
}
},
}
const EventBusService = {
emit: jest.fn(),
withTransaction: function () {
return this
},
}
describe("retrieve", () => {
const restockNotiService = new RestockNotificationService({
manager: MockManager,
productVariantService: ProductVariantService,
restockNotificationModel: RestockNotificationModel,
eventBusService: EventBusService,
})
it("successfully retrieves", async () => {
jest.clearAllMocks()
const result = await restockNotiService.retrieve("variant_1234")
expect(result).toEqual({
variant_id: "variant_1234",
emails: ["test@tesmail.com"],
})
})
it("successfully retrieves with empty response", async () => {
jest.clearAllMocks()
const result = await restockNotiService.retrieve("variant_non")
expect(result).toEqual(undefined)
})
})
describe("addEmail", () => {
const restockNotiService = new RestockNotificationService({
manager: MockManager,
productVariantService: ProductVariantService,
restockNotificationModel: RestockNotificationModel,
eventBusService: EventBusService,
})
it("successfully adds email to non-existing noti", async () => {
jest.clearAllMocks()
await restockNotiService.addEmail("variant_test", "seb@med-test.com")
expect(RestockNotificationModel.create).toHaveBeenCalledTimes(1)
expect(RestockNotificationModel.create).toHaveBeenCalledWith({
variant_id: "variant_test",
emails: ["seb@med-test.com"],
})
expect(RestockNotificationModel.save).toHaveBeenCalledTimes(1)
})
it("successfully adds email to existing noti", async () => {
jest.clearAllMocks()
await restockNotiService.addEmail("variant_1234", "seb@med-test.com")
expect(RestockNotificationModel.save).toHaveBeenCalledTimes(1)
expect(RestockNotificationModel.save).toHaveBeenCalledWith({
variant_id: "variant_1234",
emails: ["test@tesmail.com", "seb@med-test.com"],
})
})
it("fails to add if in stock", async () => {
jest.clearAllMocks()
await expect(
restockNotiService.addEmail("variant_instock", "seb@med-test.com")
).rejects.toThrow(
"You cannot sign up for restock notifications on a product that is not sold out"
)
})
})
describe("triggerRestock", () => {
const restockNotiService = new RestockNotificationService({
manager: MockManager,
productVariantService: ProductVariantService,
restockNotificationModel: RestockNotificationModel,
eventBusService: EventBusService,
})
it("non-existing noti does nothing", async () => {
jest.clearAllMocks()
await expect(restockNotiService.triggerRestock("variant_test")).resolves
})
it("existing noti but out of stock does nothing", async () => {
jest.clearAllMocks()
await expect(restockNotiService.triggerRestock("variant_outofstock"))
.resolves
})
it("existing noti emits and deletes", async () => {
jest.clearAllMocks()
await restockNotiService.triggerRestock("variant_1234")
expect(EventBusService.emit).toHaveBeenCalledTimes(1)
expect(EventBusService.emit).toHaveBeenCalledWith(
"restock-notification.restocked",
{
variant_id: "variant_1234",
emails: ["test@tesmail.com"],
}
)
expect(RestockNotificationModel.delete).toHaveBeenCalledTimes(1)
expect(RestockNotificationModel.delete).toHaveBeenCalledWith(
"variant_1234"
)
})
})
})

View File

@@ -0,0 +1,132 @@
import { MedusaError } from "medusa-core-utils"
import { BaseService } from "medusa-interfaces"
/**
* Restock notifications can be used to keep track of customers who wish to be
* notified when a certain item is restocked. Restock notifications can only
* apply to sold out items and will be deleted once items are restocked.
*/
class RestockNotificationService extends BaseService {
constructor(
{
manager,
eventBusService,
productVariantService,
restockNotificationModel,
},
options
) {
super()
this.manager_ = manager
this.options_ = options
this.productVariantService_ = productVariantService
this.restockNotificationModel_ = restockNotificationModel
this.eventBus_ = eventBusService
}
withTransaction(transactionManager) {
if (!transactionManager) {
return this
}
const cloned = new RestockNotificationService({
manager: transactionManager,
options: this.options_,
eventBusService: this.eventBus_,
productVariantService: this.productVariantService_,
})
cloned.transactionManager_ = transactionManager
return cloned
}
/**
* Retrieves a restock notification by a given variant id.
* @param {string} variantId - the variant id to retrieve restock notification
* for
* @return {Promise<RestockNotification>} The restock notification
*/
async retrieve(variantId) {
const restockRepo = this.manager_.getRepository(
this.restockNotificationModel_
)
return await restockRepo.findOne({ where: { variant_id: variantId } })
}
/**
* Adds an email to be notified when a certain variant is restocked. Throws if
* the variant is not sold out.
* @param {string} variantId - the variant id to sign up for notifications for
* @param {string} email - the email to signup
* @return {Promise<RestockNotification>} The resulting restock notification
*/
async addEmail(variantId, email) {
return this.atomicPhase_(async (manager) => {
const restockRepo = manager.getRepository(this.restockNotificationModel_)
const existing = await this.retrieve(variantId)
if (existing) {
// Converting to a set handles duplicates for us
const emailSet = new Set(existing.emails)
emailSet.add(email)
existing.emails = Array.from(emailSet)
return await restockRepo.save(existing)
} else {
const variant = await this.productVariantService_.retrieve(variantId)
if (variant.inventory_quantity > 0) {
throw new MedusaError(
MedusaError.Types.NOT_ALLOWED,
"You cannot sign up for restock notifications on a product that is not sold out"
)
}
const created = restockRepo.create({
variant_id: variant.id,
emails: [email],
})
return await restockRepo.save(created)
}
})
}
/**
* Checks if anyone has signed up for restock notifications on a given variant
* and emits a restocked event to the event bus. After successful emission the
* restock notification is deleted.
* @param {string} variantId - the variant id to trigger restock for
* @return {Promise<RestockNotification>} The resulting restock notification
*/
async triggerRestock(variantId) {
return this.atomicPhase_(async (manager) => {
const restockRepo = manager.getRepository(this.restockNotificationModel_)
const existing = await this.retrieve(variantId)
if (!existing) {
return
}
const variant = await this.productVariantService_.retrieve(variantId)
if (variant.inventory_quantity > 0) {
await this.eventBus_
.withTransaction(manager)
.emit("restock-notification.restocked", {
variant_id: variantId,
emails: existing.emails,
})
await restockRepo.delete(variantId)
}
})
}
}
export default RestockNotificationService

View File

@@ -0,0 +1,25 @@
class VariantSubscriber {
constructor({ manager, eventBusService, restockNotificationService }) {
this.manager_ = manager
this.restockNotificationService_ = restockNotificationService
eventBusService.subscribe(
"product-variant.updated",
this.handleVariantUpdate
)
}
handleVariantUpdate = async (data) => {
const { id, fields } = data
if (fields.includes("inventory_quantity")) {
return await this.manager_.transaction(
async (m) =>
await this.restockNotificationService_
.withTransaction(m)
.triggerRestock(id)
)
}
}
}
export default VariantSubscriber

View File

@@ -0,0 +1,15 @@
{
"compilerOptions": {
"lib": [
"es5",
"es6"
],
"target": "es5",
"esModuleInterop": true,
"module": "commonjs",
"moduleResolution": "node",
"emitDecoratorMetadata": true,
"experimentalDecorators": true,
"sourceMap": true
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -121,6 +121,8 @@ class SendGridService extends NotificationService {
return this.userPasswordResetData(eventData, attachmentGenerator)
case "customer.password_reset":
return this.customerPasswordResetData(eventData, attachmentGenerator)
case "restock-notification.restocked":
return await this.restockNotificationData(eventData, attachmentGenerator)
default:
return {}
}
@@ -154,6 +156,8 @@ class SendGridService extends NotificationService {
return this.options_.user_password_reset_template
case "customer.password_reset":
return this.options_.customer_password_reset_template
case "restock-notification.restocked":
return this.options_.medusa_restock_template
default:
return null
}
@@ -671,6 +675,19 @@ class SendGridService extends NotificationService {
}
}
async restockNotificationData({ variant_id, emails }) {
const variant = await this.productVariantService_.retrieve(variant_id, {
relations: ["product"]
})
return {
product: variant.product,
variant,
variant_id,
emails
}
}
userPasswordResetData(data) {
return data
}
@@ -698,7 +715,9 @@ class SendGridService extends NotificationService {
}
const normalized = humanizeAmount(amount, currency)
return normalized.toFixed(zeroDecimalCurrencies.includes(currency.toLowerCase()) ? 0 : 2)
return normalized.toFixed(
zeroDecimalCurrencies.includes(currency.toLowerCase()) ? 0 : 2
)
}
normalizeThumbUrl_(url) {

View File

@@ -1,16 +1,6 @@
class OrderSubscriber {
constructor({
totalsService,
orderService,
sendgridService,
notificationService,
fulfillmentService,
}) {
this.orderService_ = orderService
this.totalsService_ = totalsService
this.sendgridService_ = sendgridService
constructor({ notificationService }) {
this.notificationService_ = notificationService
this.fulfillmentService_ = fulfillmentService
this.notificationService_.subscribe("order.shipment_created", "sendgrid")
this.notificationService_.subscribe("order.gift_card_created", "sendgrid")

View File

@@ -0,0 +1,41 @@
class RestockNotification {
constructor({ eventBusService, sendgridService }) {
eventBusService.subscribe(
"restock-notification.restocked",
async (eventData) => {
const templateId = await sendgridService.getTemplateId(
"restock-notification.restocked"
)
if (!templateId) {
return
}
const data = await sendgridService.fetchData(
"restock-notification.restocked",
eventData,
null
)
if (!data.emails) {
return
}
return await Promise.all(
data.emails.map(async (e) => {
const sendOptions = {
template_id: templateId,
from: sendgridService.options_.from,
to: e,
dynamic_template_data: data,
}
return await sendgridService.sendEmail(sendOptions)
})
)
}
)
}
}
export default RestockNotification

View File

@@ -1,4 +1,8 @@
export default {
getRepository: function (repo) {
return repo;
},
getCustomRepository: function (repo) {
return repo;
},

View File

@@ -10,10 +10,12 @@ class MockRepo {
findOneOrFail,
save,
findAndCount,
del,
}) {
this.create_ = create;
this.update_ = update;
this.remove_ = remove;
this.delete_ = del;
this.softRemove_ = softRemove;
this.find_ = find;
this.findOne_ = findOne;
@@ -93,6 +95,12 @@ class MockRepo {
}
return {};
});
delete = jest.fn().mockImplementation((...args) => {
if (this.delete_) {
return this.delete_(...args);
}
return {};
});
}
export default (methods = {}) => {

View File

@@ -19,6 +19,7 @@
"@babel/node": "^7.7.4",
"@babel/plugin-proposal-class-properties": "^7.7.4",
"@babel/plugin-proposal-decorators": "^7.12.1",
"@babel/plugin-transform-classes": "^7.9.5",
"@babel/plugin-transform-instanceof": "^7.8.3",
"@babel/plugin-transform-runtime": "^7.7.6",
"@babel/preset-env": "^7.7.5",
@@ -47,7 +48,6 @@
"typeorm": "0.2.x"
},
"dependencies": {
"@babel/plugin-transform-classes": "^7.9.5",
"@hapi/joi": "^16.1.8",
"@types/lodash": "^4.14.168",
"awilix": "^4.2.3",

View File

@@ -11,7 +11,7 @@ import modelsLoader from "./models"
import servicesLoader from "./services"
import subscribersLoader from "./subscribers"
import passportLoader from "./passport"
import pluginsLoader from "./plugins"
import pluginsLoader, { registerPluginModels } from "./plugins"
import defaultsLoader from "./defaults"
import Logger from "./logger"
import { getManager } from "typeorm"
@@ -64,6 +64,9 @@ export default async ({ directory: rootDirectory, expressApp }) => {
await modelsLoader({ container })
Logger.info("Models initialized")
await registerPluginModels({ rootDirectory, container })
Logger.info("Models initialized")
await repositoriesLoader({ container })
Logger.info("Repositories initialized")

View File

@@ -1,4 +1,5 @@
import glob from "glob"
import { EntitySchema } from "typeorm"
import {
BaseModel,
BaseService,
@@ -12,13 +13,32 @@ import { getConfigFile, createRequireFromPath } from "medusa-core-utils"
import _ from "lodash"
import path from "path"
import fs from "fs"
import { asFunction, aliasTo } from "awilix"
import { asValue, asClass, asFunction, aliasTo } from "awilix"
import { sync as existsSync } from "fs-exists-cached"
/**
* Registers all services in the services directory
*/
export default async ({ rootDirectory, container, app }) => {
const resolved = getResolvedPlugins(rootDirectory)
await Promise.all(
resolved.map(async pluginDetails => {
registerRepositories(pluginDetails, container)
await registerServices(pluginDetails, container)
registerMedusaApi(pluginDetails, container)
registerApi(pluginDetails, app, rootDirectory, container)
registerCoreRouters(pluginDetails, container)
registerSubscribers(pluginDetails, container)
})
)
await Promise.all(
resolved.map(async pluginDetails => runLoaders(pluginDetails, container))
)
}
function getResolvedPlugins(rootDirectory) {
const { configModule } = getConfigFile(rootDirectory, `medusa-config`)
if (!configModule) {
@@ -46,20 +66,16 @@ export default async ({ rootDirectory, container, app }) => {
version: createFileContentHash(process.cwd(), `**`),
})
return resolved
}
export async function registerPluginModels({ rootDirectory, container }) {
const resolved = getResolvedPlugins(rootDirectory)
await Promise.all(
resolved.map(async pluginDetails => {
registerModels(pluginDetails, container)
await registerServices(pluginDetails, container)
registerMedusaApi(pluginDetails, container)
registerApi(pluginDetails, app, rootDirectory, container)
registerCoreRouters(pluginDetails, container)
registerSubscribers(pluginDetails, container)
})
)
await Promise.all(
resolved.map(async pluginDetails => runLoaders(pluginDetails, container))
)
}
async function runLoaders(pluginDetails, container) {
@@ -288,6 +304,33 @@ function registerSubscribers(pluginDetails, container) {
})
}
/**
* Registers a plugin's models at the right location in our container. Models
* must inherit from BaseModel. Models are registered directly in the container.
* Names are camelCase formatted and namespaced by the folder i.e:
* models/example-person -> examplePersonModel
* @param {object} pluginDetails - the plugin details including plugin options,
* version, id, resolved path, etc. See resolvePlugin
* @param {object} container - the container where the services will be
* registered
* @return {void}
*/
function registerRepositories(pluginDetails, container) {
const files = glob.sync(`${pluginDetails.resolve}/repositories/*.js`, {})
files.forEach(fn => {
const loaded = require(fn)
Object.entries(loaded).map(([key, val]) => {
if (typeof val === "function") {
const name = formatRegistrationName(fn)
container.register({
[name]: asClass(val),
})
}
})
})
}
/**
* Registers a plugin's models at the right location in our container. Models
* must inherit from BaseModel. Models are registered directly in the container.
@@ -302,20 +345,17 @@ function registerSubscribers(pluginDetails, container) {
function registerModels(pluginDetails, container) {
const files = glob.sync(`${pluginDetails.resolve}/models/*.js`, {})
files.forEach(fn => {
const loaded = require(fn).default
const loaded = require(fn)
if (!(loaded.prototype instanceof BaseModel)) {
const logger = container.resolve("logger")
const message = `Models must inherit from BaseModel, please check ${fn}`
logger.error(message)
throw new Error(message)
}
Object.entries(loaded).map(([key, val]) => {
if (typeof val === "function" || val instanceof EntitySchema) {
const name = formatRegistrationName(fn)
container.register({
[name]: asValue(val),
})
const name = formatRegistrationName(fn)
container.register({
[name]: asFunction(
cradle => new loaded(cradle, pluginDetails.options)
).singleton(),
container.registerAdd("db_entities", asValue(val))
}
})
})
}

View File

@@ -97,7 +97,6 @@ class ClaimItemService extends BaseService {
if (existing) {
return existing
}
return claimTagRepo.create({ value: normalized })
})
)

View File

@@ -5662,21 +5662,21 @@ media-typer@0.3.0:
resolved "https://registry.yarnpkg.com/media-typer/-/media-typer-0.3.0.tgz#8710d7af0aa626f8fffa1ce00168545263255748"
integrity sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=
medusa-core-utils@^1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/medusa-core-utils/-/medusa-core-utils-1.1.0.tgz#0641b365b769dbf99856025d935eef5cf5d81f2c"
integrity sha512-zocRthKhLK3eSjrXbAhZZkIMBRxyvU7GcAMFh5UCEgfe7f935vjE7r5lGTr5jTEwgwaoTUk9ep0VBekz0SEdyw==
medusa-core-utils@^1.1.4:
version "1.1.4"
resolved "https://registry.yarnpkg.com/medusa-core-utils/-/medusa-core-utils-1.1.4.tgz#ec2bb98c83426d7033632cd225b3b5dc62c26f1a"
integrity sha512-SzFfMmNbE9ukSfhapJOuYEksOKDo3yYSCeuBLFWnCZZRDnUV4ttH4Yp/ydT+cZKtqZwF2vKceXNbrT4uJYjHgw==
dependencies:
joi "^17.3.0"
joi-objectid "^3.0.1"
medusa-test-utils@^1.1.3:
version "1.1.3"
resolved "https://registry.yarnpkg.com/medusa-test-utils/-/medusa-test-utils-1.1.3.tgz#c2b45d44b9567fa2255e936d7bed73a31dfb42dd"
integrity sha512-0saYG5BhEjc4BZP76/2IJL7CyqIdbCasAci+EYXzwnwgS+nCUhKDjzzNAnC+PZMK/teD3M7x4n7isFtjgNSIDQ==
medusa-test-utils@^1.1.7:
version "1.1.7"
resolved "https://registry.yarnpkg.com/medusa-test-utils/-/medusa-test-utils-1.1.7.tgz#19d0bdf3f6f7fef0bc7f2f8e258f8c66167c692f"
integrity sha512-kcN4oJjUEAkFeko7DEaok9Qy3aty41gEzGSdR4F3KPORXZJ+YADHuj4F219/X1k+3iGqCUgmNQ9eKl6Aobq44g==
dependencies:
"@babel/plugin-transform-classes" "^7.9.5"
medusa-core-utils "^1.1.0"
medusa-core-utils "^1.1.4"
randomatic "^3.1.1"
merge-descriptors@1.0.1: