Merge branch 'integration/dummy-project' of github.com:medusajs/medusa into integration/dummy-project

This commit is contained in:
Sebastian Rindom
2020-07-04 12:16:37 +02:00
67 changed files with 13988 additions and 178 deletions
File diff suppressed because it is too large Load Diff
+3 -2
View File
@@ -1,7 +1,8 @@
{
"plugins": [
"@babel/plugin-proposal-class-properties",
"@babel/plugin-transform-instanceof"
"@babel/plugin-transform-instanceof",
"@babel/plugin-transform-classes"
],
"presets": ["@babel/preset-env"],
"env": {
@@ -9,4 +10,4 @@
"plugins": ["@babel/plugin-transform-runtime"]
}
}
}
}
@@ -0,0 +1,9 @@
{
"plugins": ["prettier"],
"extends": ["prettier"],
"rules": {
"prettier/prettier": "error",
"semi": "error",
"no-unused-expressions": "true"
}
}
@@ -0,0 +1,7 @@
{
"endOfLine": "lf",
"semi": false,
"singleQuote": false,
"tabWidth": 2,
"trailingComma": "es5"
}
@@ -0,0 +1,94 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports["default"] = void 0;
var _medusaInterfaces = _interopRequireDefault(require("medusa-interfaces"));
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { "default": obj }; }
function _instanceof(left, right) { if (right != null && typeof Symbol !== "undefined" && right[Symbol.hasInstance]) { return !!right[Symbol.hasInstance](left); } else { return left instanceof right; } }
function _typeof(obj) { "@babel/helpers - typeof"; if (typeof Symbol === "function" && typeof Symbol.iterator === "symbol") { _typeof = function _typeof(obj) { return typeof obj; }; } else { _typeof = function _typeof(obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }; } return _typeof(obj); }
function _classCallCheck(instance, Constructor) { if (!_instanceof(instance, Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
function _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } }
function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); return Constructor; }
function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function"); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, writable: true, configurable: true } }); if (superClass) _setPrototypeOf(subClass, superClass); }
function _setPrototypeOf(o, p) { _setPrototypeOf = Object.setPrototypeOf || function _setPrototypeOf(o, p) { o.__proto__ = p; return o; }; return _setPrototypeOf(o, p); }
function _createSuper(Derived) { var hasNativeReflectConstruct = _isNativeReflectConstruct(); return function () { var Super = _getPrototypeOf(Derived), result; if (hasNativeReflectConstruct) { var NewTarget = _getPrototypeOf(this).constructor; result = Reflect.construct(Super, arguments, NewTarget); } else { result = Super.apply(this, arguments); } return _possibleConstructorReturn(this, result); }; }
function _possibleConstructorReturn(self, call) { if (call && (_typeof(call) === "object" || typeof call === "function")) { return call; } return _assertThisInitialized(self); }
function _assertThisInitialized(self) { if (self === void 0) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return self; }
function _isNativeReflectConstruct() { if (typeof Reflect === "undefined" || !Reflect.construct) return false; if (Reflect.construct.sham) return false; if (typeof Proxy === "function") return true; try { Date.prototype.toString.call(Reflect.construct(Date, [], function () {})); return true; } catch (e) { return false; } }
function _getPrototypeOf(o) { _getPrototypeOf = Object.setPrototypeOf ? Object.getPrototypeOf : function _getPrototypeOf(o) { return o.__proto__ || Object.getPrototypeOf(o); }; return _getPrototypeOf(o); }
function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }
var ManualFulfillmentService = /*#__PURE__*/function (_BaseFulfillmentServi) {
_inherits(ManualFulfillmentService, _BaseFulfillmentServi);
var _super = _createSuper(ManualFulfillmentService);
function ManualFulfillmentService() {
_classCallCheck(this, ManualFulfillmentService);
return _super.call(this);
}
_createClass(ManualFulfillmentService, [{
key: "getFulfillmentOptions",
value: function getFulfillmentOptions() {
return [{
id: "manual-fulfillment"
}];
}
}, {
key: "validateFulfillmentData",
value: function validateFulfillmentData(data, cart) {
return data;
}
}, {
key: "validateOption",
value: function validateOption(data) {
if (data.id === "manual-fulfillment") {
return true;
}
return false;
}
}, {
key: "canCalculate",
value: function canCalculate() {
return false;
}
}, {
key: "calculatePrice",
value: function calculatePrice() {
throw Error("Manual Fulfillment service cannot calculatePrice");
}
}, {
key: "createOrder",
value: function createOrder() {
// No data is being sent anywhere
return;
}
}]);
return ManualFulfillmentService;
}(_medusaInterfaces["default"]);
_defineProperty(ManualFulfillmentService, "identifier", "manual");
var _default = ManualFulfillmentService;
exports["default"] = _default;
@@ -1,6 +1,6 @@
{
"name": "medusa-fulfillment-manual",
"version": "0.1.27-alpha.0",
"version": "1.0",
"description": "A manual fulfillment provider for Medusa",
"main": "index.js",
"repository": {
@@ -23,7 +23,7 @@
"jest": "^25.5.2"
},
"scripts": {
"build": "babel src --out-dir . --ignore **/__tests__",
"build": "babel src -d dist",
"prepare": "cross-env NODE_ENV=production npm run build",
"watch": "babel -w src --out-dir . --ignore **/__tests__"
},
@@ -32,7 +32,6 @@
"@babel/plugin-transform-instanceof": "^7.8.3",
"@babel/runtime": "^7.7.6",
"express": "^4.17.1",
"medusa-core-utils": "^0.3.0",
"medusa-interfaces": "^0.1.27-alpha.0"
"medusa-core-utils": "^0.3.0"
}
}
}
@@ -1,6 +1,6 @@
import { FulfillmentService } from "medusa-interfaces"
class ManualFulfillmentService extends FulfillmentService {
class ManualFulfillmentService extends FulfillmentService {
static identifier = "manual"
constructor() {
@@ -8,9 +8,11 @@ class ManualFulfillmentService extends FulfillmentService {
}
getFulfillmentOptions() {
return [{
id: "manual-fulfillment"
}]
return [
{
id: "manual-fulfillment",
},
]
}
validateFulfillmentData(data, cart) {
@@ -18,11 +20,7 @@ class ManualFulfillmentService extends FulfillmentService {
}
validateOption(data) {
if (data.id === "manual-fulfillment") {
return true
}
return false
return true
}
canCalculate() {
+2 -2
View File
@@ -27,7 +27,7 @@
"medusa-test-utils": "^0.3.0"
},
"scripts": {
"build": "babel src -d dist",
"build": "babel src -d .",
"prepare": "cross-env NODE_ENV=production npm run build",
"watch": "babel -w src --out-dir . --ignore **/__tests__",
"test": "jest"
@@ -42,4 +42,4 @@
"body-parser": "^1.19.0"
},
"gitHead": "35e0930650d5f4aedf2610749cd131ae8b7e17cc"
}
}
@@ -3,7 +3,7 @@ export default async (req, res) => {
let event
try {
const stripeProviderService = req.resolve("pp_stripe")
const stripeProviderService = req.scope.resolve("pp_stripe")
event = stripeProviderService.constructWebhookEvent(req.body, signature)
} catch (err) {
res.status(400).send(`Webhook Error: ${err.message}`)
@@ -103,7 +103,7 @@ class StripeProviderService extends PaymentService {
const paymentIntent = await this.stripe_.paymentIntents.create({
customer: stripeCustomerId,
amount: amount * 100, // Stripe amount is in cents
currency: currency_code
currency: currency_code,
})
return paymentIntent
@@ -134,7 +134,7 @@ class StripeProviderService extends PaymentService {
const { id } = data
const amount = this.totalsService_.getTotal(cart)
return this.stripe_.paymentIntents.update(id, {
amount
amount,
})
} catch (error) {
throw error
@@ -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"]
}
}
}
@@ -0,0 +1,9 @@
{
"plugins": ["prettier"],
"extends": ["prettier"],
"rules": {
"prettier/prettier": "error",
"semi": "error",
"no-unused-expressions": "true"
}
}
@@ -0,0 +1,15 @@
/lib
node_modules
.DS_store
.env*
/*.js
!index.js
yarn.lock
/dist
/api
/services
/models
/subscribers
@@ -0,0 +1,9 @@
/lib
node_modules
.DS_store
.env*
/*.js
!index.js
yarn.lock
@@ -0,0 +1,7 @@
{
"endOfLine": "lf",
"semi": false,
"singleQuote": false,
"tabWidth": 2,
"trailingComma": "es5"
}
@@ -0,0 +1 @@
// noop
@@ -0,0 +1,44 @@
{
"name": "medusa-plugin-contentful",
"version": "1.0.0",
"description": "Contentful plugin for Medusa Commerce",
"main": "index.js",
"repository": {
"type": "git",
"url": "https://github.com/medusajs/medusa",
"directory": "packages/medusa-plugin-contentful"
},
"author": "Oliver Juhl",
"license": "AGPL-3.0-or-later",
"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"
},
"scripts": {
"build": "babel src -d dist",
"prepare": "cross-env NODE_ENV=production npm run build",
"watch": "babel -w src --out-dir . --ignore **/__tests__",
"test": "jest"
},
"dependencies": {
"@babel/plugin-transform-classes": "^7.9.5",
"body-parser": "^1.19.0",
"contentful-management": "^5.27.1",
"express": "^4.17.1",
"medusa-core-utils": "^0.3.0",
"medusa-interfaces": "^0.3.0",
"medusa-test-utils": "^0.3.0",
"redis": "^3.0.2"
}
}
@@ -0,0 +1,10 @@
import { Router } from "express"
import hooks from "./routes/hooks"
export default (container) => {
const app = Router()
hooks(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,
}
@@ -0,0 +1,26 @@
export default async (req, res) => {
try {
const contentfulService = req.scope.resolve("contentfulService")
const contentfulType = req.body.sys.contentType.sys.id
const entryId = req.body.sys.id
let updated = {}
switch (contentfulType) {
case "product":
updated = await contentfulService.sendContentfulProductToAdmin(entryId)
break
case "productVariant":
updated = await contentfulService.sendContentfulProductVariantToAdmin(
entryId
)
break
default:
break
}
res.status(200).send(updated)
} catch (error) {
res.status(400).send(`Webhook error: ${error.message}`)
}
}
@@ -0,0 +1,13 @@
import { Router } from "express"
import bodyParser from "body-parser"
import middlewares from "../../middlewares"
const route = Router()
export default (app) => {
app.use("/hooks", route)
route.post("/contentful", middlewares.wrap(require("./contentful").default))
return app
}
@@ -0,0 +1,296 @@
import _ from "lodash"
import { BaseService } from "medusa-interfaces"
import { createClient } from "contentful-management"
import redis from "redis"
class ContentfulService extends BaseService {
constructor(
{ productService, productVariantService, eventBusService },
options
) {
super()
this.productService_ = productService
this.productVariantService_ = productVariantService
this.eventBus_ = eventBusService
this.options_ = options
this.contentful_ = createClient({
accessToken: options.access_token,
})
this.redis_ = redis.createClient()
}
async getIgnoreIds_(type) {
return new Promise((resolve, reject) => {
this.redis_.get(`${type}_ignore_ids`, (err, reply) => {
if (err) {
return reject(err)
}
return resolve(JSON.parse(reply))
})
})
}
async getContentfulEnvironment_() {
try {
const space = await this.contentful_.getSpace(this.options_.space_id)
const environment = await space.getEnvironment(this.options_.environment)
return environment
} catch (error) {
throw error
}
}
async getVariantEntries_(productId) {
try {
const productVariants = await this.productService_.retrieveVariants(
productId
)
const contentfulVariants = await Promise.all(
productVariants.map((variant) =>
this.updateProductVariantInContentful(variant)
)
)
return contentfulVariants
} catch (error) {
console.log(error)
throw error
}
}
getVariantLinks_(variantEntries) {
return variantEntries.map((v) => ({
sys: {
type: "Link",
linkType: "Entry",
id: v.sys.id,
},
}))
}
async createProductInContentful(product) {
try {
const environment = await this.getContentfulEnvironment_()
const variantEntries = await this.getVariantEntries_(product._id)
const variantLinks = this.getVariantLinks_(variantEntries)
const result = await environment.createEntryWithId(
"product",
product._id,
{
fields: {
title: {
"en-US": product.title,
},
variants: {
"en-US": variantLinks,
},
objectId: {
"en-US": product._id,
},
},
}
)
const ignoreIds = (await this.getIgnoreIds_("product")) || []
ignoreIds.push(product._id)
this.redis_.set("product_ignore_ids", JSON.stringify(ignoreIds))
return result
} catch (error) {
throw error
}
}
async createProductVariantInContentful(variant) {
try {
const environment = await this.getContentfulEnvironment_()
const result = await environment.createEntryWithId(
"productVariant",
variant._id,
{
fields: {
title: {
"en-US": variant.title,
},
sku: {
"en-US": variant.sku,
},
prices: {
"en-US": variant.prices,
},
objectId: {
"en-US": variant._id,
},
},
}
)
const ignoreIds = (await this.getIgnoreIds_("product_variant")) || []
ignoreIds.push(variant._id)
this.redis_.set("product_variant_ignore_ids", JSON.stringify(ignoreIds))
return result
} catch (error) {
throw error
}
}
async updateProductInContentful(product) {
try {
const ignoreIds = (await this.getIgnoreIds_("product")) || []
if (ignoreIds.includes(product._id)) {
const newIgnoreIds = ignoreIds.filter((id) => id !== product._id)
this.redis_.set("product_ignore_ids", JSON.stringify(newIgnoreIds))
return
} else {
ignoreIds.push(product._id)
this.redis_.set("product_ignore_ids", JSON.stringify(ignoreIds))
}
const environment = await this.getContentfulEnvironment_()
// check if product exists
let productEntry = undefined
try {
productEntry = await environment.getEntry(product._id)
} catch (error) {
return this.createProductInContentful(product)
}
const variantEntries = await this.getVariantEntries_(product.variants)
const variantLinks = this.getVariantLinks_(variantEntries)
productEntry.fields = _.assignIn(productEntry.fields, {
title: {
"en-US": product.title,
},
variants: {
"en-US": variantLinks,
},
objectId: {
"en-US": product._id,
},
})
const updatedEntry = await productEntry.update()
const publishedEntry = await updatedEntry.publish()
return publishedEntry
} catch (error) {
throw error
}
}
async updateProductVariantInContentful(variant) {
try {
const ignoreIds = (await this.getIgnoreIds_("product_variant")) || []
if (ignoreIds.includes(variant._id)) {
const newIgnoreIds = ignoreIds.filter((id) => id !== variant._id)
this.redis_.set(
"product_variant_ignore_ids",
JSON.stringify(newIgnoreIds)
)
return
} else {
ignoreIds.push(variant._id)
this.redis_.set("product_variant_ignore_ids", JSON.stringify(ignoreIds))
}
const environment = await this.getContentfulEnvironment_()
// check if product exists
let variantEntry = undefined
// if not, we create a new one
try {
variantEntry = await environment.getEntry(variant._id)
} catch (error) {
return this.createProductVariantInContentful(variant)
}
variantEntry.fields = _.assignIn(variantEntry.fields, {
title: {
"en-US": variant.title,
},
sku: {
"en-US": variant.sku,
},
prices: {
"en-US": variant.prices,
},
objectId: {
"en-US": variant._id,
},
})
const updatedEntry = await variantEntry.update()
const publishedEntry = await updatedEntry.publish()
return publishedEntry
} catch (error) {
throw error
}
}
async sendContentfulProductToAdmin(productId) {
try {
const environment = await this.getContentfulEnvironment_()
const productEntry = await environment.getEntry(productId)
const ignoreIds = (await this.getIgnoreIds_("product")) || []
if (ignoreIds.includes(productId)) {
const newIgnoreIds = ignoreIds.filter((id) => id !== productId)
this.redis_.set("product_ignore_ids", JSON.stringify(newIgnoreIds))
return
} else {
ignoreIds.push(productId)
this.redis_.set("product_ignore_ids", JSON.stringify(ignoreIds))
}
const updatedProduct = await this.productService_.update(productId, {
title: productEntry.fields.title["en-US"],
})
return updatedProduct
} catch (error) {
throw error
}
}
async sendContentfulProductVariantToAdmin(variantId) {
try {
const environment = await this.getContentfulEnvironment_()
const variantEntry = await environment.getEntry(variantId)
const ignoreIds = (await this.getIgnoreIds_("product_variant")) || []
if (ignoreIds.includes(variantId)) {
const newIgnoreIds = ignoreIds.filter((id) => id !== variantId)
this.redis_.set(
"product_variant_ignore_ids",
JSON.stringify(newIgnoreIds)
)
return
} else {
ignoreIds.push(variantId)
this.redis_.set("product_variant_ignore_ids", JSON.stringify(ignoreIds))
}
const updatedVariant = await this.productVariantService_.update(
variantId,
{
title: variantEntry.fields.title["en-US"],
}
)
return updatedVariant
} catch (error) {
throw error
}
}
}
export default ContentfulService
@@ -0,0 +1,20 @@
class ContentfulSubscriber {
constructor({ contentfulService, eventBusService }) {
this.contentfulService_ = contentfulService
this.eventBus_ = eventBusService
this.eventBus_.subscribe("product-variant.updated", async (data) => {
await this.contentfulService_.updateProductVariantInContentful(data)
})
this.eventBus_.subscribe("product.updated", async (data) => {
await this.contentfulService_.updateProductInContentful(data)
})
this.eventBus_.subscribe("product.created", async (data) => {
await this.contentfulService_.createProductInContentful(data)
})
}
}
export default ContentfulSubscriber
+13
View 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"]
}
}
}
@@ -0,0 +1,9 @@
{
"plugins": ["prettier"],
"extends": ["prettier"],
"rules": {
"prettier/prettier": "error",
"semi": "error",
"no-unused-expressions": "true"
}
}
@@ -0,0 +1,15 @@
/lib
node_modules
.DS_store
.env*
/*.js
!index.js
!jest.config.js
/dist
/api
/services
/models
/subscribers
@@ -0,0 +1,9 @@
/lib
node_modules
.DS_store
.env*
/*.js
!index.js
yarn.lock
@@ -0,0 +1,7 @@
{
"endOfLine": "lf",
"semi": false,
"singleQuote": false,
"tabWidth": 2,
"trailingComma": "es5"
}
+1
View File
@@ -0,0 +1 @@
// noop
@@ -0,0 +1,3 @@
module.exports = {
testEnvironment: "node",
}
@@ -0,0 +1,43 @@
{
"name": "medusa-plugin-sendgrid",
"version": "0.3.0",
"description": "SendGrid transactional emails",
"main": "index.js",
"repository": {
"type": "git",
"url": "https://github.com/medusajs/medusa",
"directory": "packages/medusa-plugin-sendgrid"
},
"author": "Oliver Juhl",
"license": "AGPL-3.0-or-later",
"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"
},
"scripts": {
"build": "babel src -d .",
"prepare": "cross-env NODE_ENV=production npm run build",
"watch": "babel -w src --out-dir . --ignore **/__tests__",
"test": "jest"
},
"dependencies": {
"@babel/plugin-transform-classes": "^7.9.5",
"@sendgrid/mail": "^7.1.1",
"body-parser": "^1.19.0",
"express": "^4.17.1",
"medusa-core-utils": "^0.3.0",
"medusa-interfaces": "^0.3.0",
"medusa-test-utils": "^0.3.0"
}
}
@@ -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,
}
@@ -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("/sendgrid", route)
route.post(
"/send",
bodyParser.raw({ type: "application/json" }),
middlewares.wrap(require("./send-email").default)
)
return app
}
@@ -0,0 +1,28 @@
import { Validator, MedusaError } from "medusa-core-utils"
export default async (req, res) => {
const schema = Validator.object().keys({
template_id: Validator.string().required(),
from: Validator.string().required(),
to: Validator.string().required(),
data: Validator.object().optional().default({}),
})
const { value, error } = schema.validate(req.body)
if (error) {
throw new MedusaError(MedusaError.Types.INVALID_DATA, error.details)
}
try {
const sendgridService = req.scope.resolve("sendgridService")
await sendgridService.sendEmail(
value.template_id,
value.from,
value.to,
value.data
)
res.sendStatus(200)
} catch (err) {
throw err
}
}
@@ -0,0 +1,89 @@
import { BaseService } from "medusa-interfaces"
import SendGrid from "@sendgrid/mail"
class SendGridService extends BaseService {
/**
* @param {Object} options - options defined in `medusa-config.js`
* e.g.
* {
* api_key: SendGrid api key
* from: Medusa <hello@medusa.example>,
* order_placed_template: 01234,
* order_updated_template: 56789,
* order_updated_cancelled_template: 4242,
* user_password_reset_template: 0000,
* customer_password_reset_template: 1111,
* }
*/
constructor({}, options) {
super()
this.options_ = options
SendGrid.setApiKey(options.api_key)
}
/**
* Sends a transactional email based on an event using SendGrid.
* @param {string} event - event related to the order
* @param {Object} order - the order object sent to SendGrid, that must
* correlate with the structure specificed in the dynamic template
* @returns {Promise} result of the send operation
*/
async transactionalEmail(event, order) {
let templateId
switch (event) {
case "order.placed":
templateId = this.options_.order_placed_template
break
case "order.updated":
templateId = this.options_.order_updated_template
break
case "order.cancelled":
templateId = this.options_.order_cancelled_template
break
case "user.password_reset":
templateId = this.options_.user_password_reset_template
break
case "customer.password_reset":
templateId = this.options_.customer_password_reset_template
break
default:
return
}
try {
return SendGrid.send({
template_id: templateId,
from: options.from,
to: order.email,
dynamic_template_data: order,
})
} catch (error) {
throw error
}
}
/**
* Sends an email using SendGrid.
* @param {string} templateId - id of template in SendGrid
* @param {string} from - sender of email
* @param {string} to - receiver of email
* @param {Object} data - data to send in mail (match with template)
* @returns {Promise} result of the send operation
*/
async sendEmail(templateId, from, to, data) {
try {
return SendGrid.send({
to,
from,
template_id: templateId,
dynamic_template_data: data,
})
} catch (error) {
throw error
}
}
}
export default SendGridService
@@ -0,0 +1,21 @@
class OrderSubscriber {
constructor({ sendgridService, eventBusService }) {
this.sendgridService_ = sendgridService
this.eventBus_ = eventBusService
this.eventBus_.subscribe("order.placed", async (order) => {
await this.sendgridService_.transactionalEmail("order.placed", order)
})
this.eventBus_.subscribe("order.cancelled", async (order) => {
await this.sendgridService_.transactionalEmail("order.cancelled", order)
})
this.eventBus_.subscribe("order.updated", async (order) => {
await this.sendgridService_.transactionalEmail("order.updated", order)
})
}
}
export default OrderSubscriber
@@ -0,0 +1,23 @@
class UserSubscriber {
constructor({ sendgridService, eventBusService }) {
this.sendgridService_ = sendgridService
this.eventBus_ = eventBusService
this.eventBus_.subscribe("user.password_reset", async (data) => {
await this.sendgridService_.transactionalEmail(
"user.password_reset",
data
)
})
this.eventBus_.subscribe("customer.password_reset", async (data) => {
await this.sendgridService_.transactionalEmail(
"customer.password_reset",
data
)
})
}
}
export default UserSubscriber
File diff suppressed because it is too large Load Diff
+1 -1
View File
@@ -72,4 +72,4 @@
"winston": "^3.2.1"
},
"gitHead": "35e0930650d5f4aedf2610749cd131ae8b7e17cc"
}
}
@@ -43,6 +43,8 @@ export default async (req, res) => {
delete value.variants
const productService = req.scope.resolve("productService")
const shippingProfileService = req.scope.resolve("shippingProfileService")
let newProduct = await productService.createDraft(value)
if (variants) {
@@ -64,6 +66,10 @@ export default async (req, res) => {
)
}
// Add to default shipping profile
const { _id } = await shippingProfileService.retrieveDefault()
await shippingProfileService.addProduct(_id, newProduct._id)
newProduct = await productService.decorate(newProduct, [
"title",
"description",
@@ -12,6 +12,8 @@ describe("POST /admin/shipping-options", () => {
name: "Test option",
region_id: "testregion",
provider_id: "test_provider",
data: { id: "test" },
profile_id: "test",
price: {
type: "flat_rate",
amount: 100,
@@ -32,6 +34,7 @@ describe("POST /admin/shipping-options", () => {
})
it("returns 200", () => {
console.log(subject)
expect(subject.status).toEqual(200)
})
@@ -41,6 +44,8 @@ describe("POST /admin/shipping-options", () => {
name: "Test option",
region_id: "testregion",
provider_id: "test_provider",
data: { id: "test" },
profile_id: "test",
price: {
type: "flat_rate",
amount: 100,
@@ -5,11 +5,14 @@ export default async (req, res) => {
name: Validator.string().required(),
region_id: Validator.string().required(),
provider_id: Validator.string().required(),
data: Validator.object(),
price: Validator.object().keys({
type: Validator.string().required(),
amount: Validator.number().optional(),
}),
profile_id: Validator.string().required(),
data: Validator.object().required(),
price: Validator.object()
.keys({
type: Validator.string().required(),
amount: Validator.number().optional(),
})
.required(),
requirements: Validator.array()
.items(
Validator.object({
@@ -27,10 +30,17 @@ export default async (req, res) => {
try {
const optionService = req.scope.resolve("shippingOptionService")
const shippingProfileService = req.scope.resolve("shippingProfileService")
const data = await optionService.create(value)
// Add to default shipping profile
const { _id } = await shippingProfileService.retrieveDefault()
await shippingProfileService.addShippingOption(_id, data._id)
res.status(200).json({ shipping_option: data })
} catch (err) {
console.log(err)
throw err
}
}
@@ -12,6 +12,7 @@ export default async (req, res) => {
discounts: Validator.array().items({
code: Validator.string(),
}),
customer_id: Validator.string(),
})
const { value, error } = schema.validate(req.body)
@@ -27,6 +28,10 @@ export default async (req, res) => {
}
try {
if (value.customer_id) {
await cartService.updateCustomerId(id, value.customer_id)
}
if (value.region_id) {
await cartService.setRegion(id, value.region_id)
}
@@ -17,7 +17,7 @@ export default async (req, res) => {
try {
const customerService = req.scope.resolve("customerService")
const customer = await customerService.create(value)
const data = await customerService.decorate(customer)
const data = await customerService.decorate(customer, ["_id", "email"])
res.status(201).json({ customer: data })
} catch (err) {
throw err
+7
View File
@@ -0,0 +1,7 @@
export default async ({ container }) => {
const storeService = container.resolve("storeService")
const profileService = container.resolve("shippingProfileService")
await storeService.create()
await profileService.createDefault()
}
+3 -3
View File
@@ -6,7 +6,7 @@ import modelsLoader from "./models"
import servicesLoader from "./services"
import passportLoader from "./passport"
import pluginsLoader from "./plugins"
import storeLoader from "./store"
import defaultsLoader from "./defaults"
import Logger from "./logger"
export default async ({ directory: rootDirectory, expressApp }) => {
@@ -58,8 +58,8 @@ export default async ({ directory: rootDirectory, expressApp }) => {
await apiLoader({ container, rootDirectory, app: expressApp })
Logger.info("API initialized")
await storeLoader({ container })
Logger.info("Store initialized")
await defaultsLoader({ container })
Logger.info("Defaults initialized")
return { container, dbConnection, app: expressApp }
}
-7
View File
@@ -1,7 +0,0 @@
/**
* Registers all services in the services directory
*/
export default ({ container }) => {
const storeService = container.resolve("storeService")
return storeService.create()
}
@@ -13,6 +13,8 @@ class ProductVariantModel extends BaseModel {
static schema = {
title: { type: String, required: true },
prices: { type: [MoneyAmountSchema], default: [], required: true },
sku: { type: String, default: "" },
barcode: { type: String, default: "" },
options: { type: [OptionValueSchema], default: [] },
sku: { type: String, unique: true, sparse: true },
ean: { type: String, unique: true, sparse: true },
@@ -31,6 +31,9 @@ export const ShippingProfileServiceMock = {
}
return Promise.resolve()
}),
retrieveDefault: jest.fn().mockImplementation(data => {
return Promise.resolve({ _id: IdMap.getId("default_shipping_profile") })
}),
list: jest.fn().mockImplementation(selector => {
if (!selector) {
return Promise.resolve([])
@@ -5,11 +5,13 @@ import { PaymentProviderServiceMock } from "../__mocks__/payment-provider"
import { FulfillmentProviderServiceMock } from "../__mocks__/fulfillment-provider"
import { ShippingProfileServiceMock } from "../__mocks__/shipping-profile"
import { TotalsServiceMock } from "../__mocks__/totals"
import { EventBusServiceMock } from "../__mocks__/event-bus"
describe("OrderService", () => {
describe("create", () => {
const orderService = new OrderService({
orderModel: OrderModelMock,
eventBusService: EventBusServiceMock,
})
beforeEach(async () => {
@@ -54,6 +56,7 @@ describe("OrderService", () => {
describe("update", () => {
const orderService = new OrderService({
orderModel: OrderModelMock,
eventBusService: EventBusServiceMock,
})
beforeEach(async () => {
@@ -182,6 +185,7 @@ describe("OrderService", () => {
describe("cancel", () => {
const orderService = new OrderService({
orderModel: OrderModelMock,
eventBusService: EventBusServiceMock,
})
beforeEach(async () => {
@@ -252,6 +256,7 @@ describe("OrderService", () => {
paymentProviderService: PaymentProviderServiceMock,
fulfillmentProviderService: FulfillmentProviderServiceMock,
shippingProfileService: ShippingProfileServiceMock,
eventBusService: EventBusServiceMock,
})
beforeEach(async () => {
@@ -4,6 +4,7 @@ import ProductVariantService from "../product-variant"
import { ProductVariantModelMock } from "../../models/__mocks__/product-variant"
import { ProductServiceMock } from "../__mocks__/product"
import { RegionServiceMock } from "../__mocks__/region"
import { EventBusServiceMock } from "../__mocks__/event-bus"
describe("ProductVariantService", () => {
describe("retrieve", () => {
@@ -12,6 +13,7 @@ describe("ProductVariantService", () => {
beforeAll(async () => {
const productVariantService = new ProductVariantService({
productVariantModel: ProductVariantModelMock,
eventBusService: EventBusServiceMock,
})
res = await productVariantService.retrieve(IdMap.getId("validId"))
@@ -38,6 +40,7 @@ describe("ProductVariantService", () => {
beforeAll(async () => {
const productVariantService = new ProductVariantService({
productVariantModel: ProductVariantModelMock,
eventBusService: EventBusServiceMock,
})
await productVariantService
@@ -69,6 +72,7 @@ describe("ProductVariantService", () => {
jest.clearAllMocks()
const productVariantService = new ProductVariantService({
productVariantModel: ProductVariantModelMock,
eventBusService: EventBusServiceMock,
})
productVariantService.createDraft({
@@ -106,6 +110,7 @@ describe("ProductVariantService", () => {
jest.clearAllMocks()
const productVariantService = new ProductVariantService({
productVariantModel: ProductVariantModelMock,
eventBusService: EventBusServiceMock,
})
productVariantService.publish(IdMap.getId("variantId"))
@@ -124,6 +129,7 @@ describe("ProductVariantService", () => {
describe("update", () => {
const productVariantService = new ProductVariantService({
productVariantModel: ProductVariantModelMock,
eventBusService: EventBusServiceMock,
})
beforeEach(() => {
@@ -168,6 +174,7 @@ describe("ProductVariantService", () => {
describe("decorate", () => {
const productVariantService = new ProductVariantService({
productVariantModel: ProductVariantModelMock,
eventBusService: EventBusServiceMock,
})
const fakeVariant = {
@@ -216,6 +223,7 @@ describe("ProductVariantService", () => {
describe("setMetadata", () => {
const productVariantService = new ProductVariantService({
productVariantModel: ProductVariantModelMock,
eventBusService: EventBusServiceMock,
})
beforeEach(() => {
@@ -264,6 +272,7 @@ describe("ProductVariantService", () => {
const productVariantService = new ProductVariantService({
productVariantModel: ProductVariantModelMock,
productService: ProductServiceMock,
eventBusService: EventBusServiceMock,
})
beforeEach(() => {
@@ -331,6 +340,7 @@ describe("ProductVariantService", () => {
const productVariantService = new ProductVariantService({
productVariantModel: ProductVariantModelMock,
productService: ProductServiceMock,
eventBusService: EventBusServiceMock,
})
beforeEach(() => {
@@ -354,6 +364,7 @@ describe("ProductVariantService", () => {
describe("delete", () => {
const productVariantService = new ProductVariantService({
productVariantModel: ProductVariantModelMock,
eventBusService: EventBusServiceMock,
})
beforeEach(() => {
@@ -373,6 +384,7 @@ describe("ProductVariantService", () => {
describe("canCoverQuantity", () => {
const productVariantService = new ProductVariantService({
productVariantModel: ProductVariantModelMock,
eventBusService: EventBusServiceMock,
})
beforeEach(() => {
@@ -419,6 +431,7 @@ describe("ProductVariantService", () => {
describe("setCurrencyPrice", () => {
const productVariantService = new ProductVariantService({
productVariantModel: ProductVariantModelMock,
eventBusService: EventBusServiceMock,
})
beforeEach(() => {
@@ -519,6 +532,7 @@ describe("ProductVariantService", () => {
const productVariantService = new ProductVariantService({
productVariantModel: ProductVariantModelMock,
regionService: RegionServiceMock,
eventBusService: EventBusServiceMock,
})
beforeEach(() => {
@@ -619,6 +633,7 @@ describe("ProductVariantService", () => {
const productVariantService = new ProductVariantService({
productVariantModel: ProductVariantModelMock,
regionService: RegionServiceMock,
eventBusService: EventBusServiceMock,
})
beforeEach(() => {
@@ -6,6 +6,7 @@ import {
ProductVariantServiceMock,
variants,
} from "../__mocks__/product-variant"
import { EventBusServiceMock } from "../__mocks__/event-bus"
describe("ProductService", () => {
describe("retrieve", () => {
@@ -14,6 +15,7 @@ describe("ProductService", () => {
beforeAll(async () => {
const productService = new ProductService({
productModel: ProductModelMock,
eventBusService: EventBusServiceMock,
})
res = await productService.retrieve(IdMap.getId("validId"))
@@ -40,6 +42,7 @@ describe("ProductService", () => {
beforeAll(async () => {
const productService = new ProductService({
productModel: ProductModelMock,
eventBusService: EventBusServiceMock,
})
await productService.retrieve(IdMap.getId("failId")).catch(err => {
@@ -70,6 +73,7 @@ describe("ProductService", () => {
jest.clearAllMocks()
const productService = new ProductService({
productModel: ProductModelMock,
eventBusService: EventBusServiceMock,
})
productService.createDraft({
@@ -108,6 +112,7 @@ describe("ProductService", () => {
const productService = new ProductService({
productModel: ProductModelMock,
productVariantService: ProductVariantServiceMock,
eventBusService: EventBusServiceMock,
})
productService.publish(IdMap.getId("productId"))
@@ -127,6 +132,7 @@ describe("ProductService", () => {
const productService = new ProductService({
productModel: ProductModelMock,
productVariantService: ProductVariantServiceMock,
eventBusService: EventBusServiceMock,
})
const fakeProduct = {
@@ -193,6 +199,7 @@ describe("ProductService", () => {
describe("setMetadata", () => {
const productService = new ProductService({
productModel: ProductModelMock,
eventBusService: EventBusServiceMock,
})
beforeEach(() => {
@@ -236,6 +243,7 @@ describe("ProductService", () => {
describe("update", () => {
const productService = new ProductService({
productModel: ProductModelMock,
eventBusService: EventBusServiceMock,
})
beforeEach(() => {
@@ -290,6 +298,7 @@ describe("ProductService", () => {
const productService = new ProductService({
productModel: ProductModelMock,
productVariantService: ProductVariantServiceMock,
eventBusService: EventBusServiceMock,
})
beforeEach(() => {
@@ -314,6 +323,7 @@ describe("ProductService", () => {
const productService = new ProductService({
productModel: ProductModelMock,
productVariantService: ProductVariantServiceMock,
eventBusService: EventBusServiceMock,
})
afterEach(() => {
@@ -479,6 +489,7 @@ describe("ProductService", () => {
const productService = new ProductService({
productModel: ProductModelMock,
productVariantService: ProductVariantServiceMock,
eventBusService: EventBusServiceMock,
})
afterEach(() => {
@@ -571,6 +582,7 @@ describe("ProductService", () => {
const productService = new ProductService({
productModel: ProductModelMock,
productVariantService: ProductVariantServiceMock,
eventBusService: EventBusServiceMock,
})
afterEach(() => {
@@ -632,6 +644,7 @@ describe("ProductService", () => {
const productService = new ProductService({
productModel: ProductModelMock,
productVariantService: ProductVariantServiceMock,
eventBusService: EventBusServiceMock,
})
afterEach(() => {
@@ -659,6 +672,7 @@ describe("ProductService", () => {
const productService = new ProductService({
productModel: ProductModelMock,
productVariantService: ProductVariantServiceMock,
eventBusService: EventBusServiceMock,
})
afterEach(() => {
@@ -719,6 +733,7 @@ describe("ProductService", () => {
const productService = new ProductService({
productModel: ProductModelMock,
productVariantService: ProductVariantServiceMock,
eventBusService: EventBusServiceMock,
})
afterEach(() => {
@@ -788,6 +803,7 @@ describe("ProductService", () => {
const productService = new ProductService({
productModel: ProductModelMock,
productVariantService: ProductVariantServiceMock,
eventBusService: EventBusServiceMock,
})
afterEach(() => {
@@ -843,6 +859,7 @@ describe("ProductService", () => {
const productService = new ProductService({
productModel: ProductModelMock,
productVariantService: ProductVariantServiceMock,
eventBusService: EventBusServiceMock,
})
afterEach(() => {
+34
View File
@@ -370,6 +370,40 @@ class CartService extends BaseService {
return result
})
}
/**
* Sets the customer id of a cart
* @param {string} cartId - the id of the cart to add email to
* @param {string} customerId - the customer to add to cart
* @return {Promise} the result of the update operation
*/
async updateCustomerId(cartId, customerId) {
const cart = await this.retrieve(cartId)
const schema = Validator.string()
.objectId()
.required()
const { value, error } = schema.validate(customerId)
if (error) {
throw new MedusaError(
MedusaError.Types.INVALID_DATA,
"The customerId is not valid"
)
}
return this.cartModel_
.updateOne(
{
_id: cart._id,
},
{
$set: { customer_id: value },
}
)
.then(result => {
// Notify subscribers
this.eventBus_.emit(CartService.Events.UPDATED, result)
return result
})
}
/**
* Sets the email of a cart
+10 -5
View File
@@ -9,6 +9,10 @@ import { BaseService } from "medusa-interfaces"
* @implements BaseService
*/
class CustomerService extends BaseService {
static Events = {
PASSWORD_RESET: "customer.password_reset",
}
constructor({ customerModel, eventBusService }) {
super()
@@ -26,7 +30,7 @@ class CustomerService extends BaseService {
*/
validateId_(rawId) {
const schema = Validator.objectId()
const { value, error } = schema.validate(rawId)
const { value, error } = schema.validate(rawId.toString())
if (error) {
throw new MedusaError(
MedusaError.Types.INVALID_ARGUMENT,
@@ -92,10 +96,11 @@ class CustomerService extends BaseService {
const expiry = Math.floor(Date.now() / 1000) + 60 * 15 // 15 minutes ahead
const payload = { customer_id: customer._id, exp: expiry }
const token = jwt.sign(payload, secret)
// TODO: Call event layer to ensure that there is an email service that
// sends the token.
// Notify subscribers
this.eventBus_.emit(CustomerService.Events.PASSWORD_RESET, {
email: customer.email,
token,
})
return token
}
+1 -1
View File
@@ -35,7 +35,7 @@ class DiscountService extends BaseService {
*/
validateId_(rawId) {
const schema = Validator.objectId()
const { value, error } = schema.validate(rawId)
const { value, error } = schema.validate(rawId.toString())
if (error) {
throw new MedusaError(
MedusaError.Types.INVALID_ARGUMENT,
@@ -14,7 +14,7 @@ class FulfillmentProviderService {
*/
retrieveProvider(provider_id) {
try {
return this.container_.resolve(`fp_${provider_id}`)
return this.container_[`fp_${provider_id}`]
} catch (err) {
throw new MedusaError(
MedusaError.Types.NOT_FOUND,
+64 -20
View File
@@ -3,6 +3,12 @@ import { Validator, MedusaError } from "medusa-core-utils"
import { BaseService } from "medusa-interfaces"
class OrderService extends BaseService {
static Events = {
PLACED: "order.placed",
UPDATED: "order.updated",
CANCELLED: "order.cancelled",
}
constructor({
orderModel,
paymentProviderService,
@@ -43,7 +49,7 @@ class OrderService extends BaseService {
*/
validateId_(rawId) {
const schema = Validator.objectId()
const { value, error } = schema.validate(rawId)
const { value, error } = schema.validate(rawId.toString())
if (error) {
throw new MedusaError(
MedusaError.Types.INVALID_ARGUMENT,
@@ -139,15 +145,30 @@ class OrderService extends BaseService {
return order
}
/**
* @param {Object} selector - the query object for find
* @return {Promise} the result of the find operation
*/
list(selector) {
return this.orderModel_.find(selector)
}
/**
* Creates an order
* @param {object} order - the order to create
* @return {Promise} resolves to the creation result.
*/
async create(order) {
return this.orderModel_.create(order).catch(err => {
throw new MedusaError(MedusaError.Types.DB_ERROR, err.message)
})
return this.orderModel_
.create(order)
.then(result => {
// Notify subscribers
this.eventBus_.emit(OrderService.Events.PLACED, result)
return result
})
.catch(err => {
throw new MedusaError(MedusaError.Types.DB_ERROR, err.message)
})
}
/**
@@ -217,6 +238,11 @@ class OrderService extends BaseService {
{ $set: updateFields },
{ runValidators: true }
)
.then(result => {
// Notify subscribers
this.eventBus_.emit(OrderService.Events.UPDATED, result)
return result
})
.catch(err => {
throw new MedusaError(MedusaError.Types.DB_ERROR, err.message)
})
@@ -248,14 +274,23 @@ class OrderService extends BaseService {
// TODO: cancel payment method
return this.orderModel_.updateOne(
{
_id: orderId,
},
{
$set: { status: "cancelled" },
}
)
return this.orderModel_
.updateOne(
{
_id: orderId,
},
{
$set: { status: "cancelled" },
}
)
.then(result => {
// Notify subscribers
this.eventBus_.emit(OrderService.Events.CANCELLED, result)
return result
})
.catch(err => {
throw new MedusaError(MedusaError.Types.DB_ERROR, err.message)
})
}
/**
@@ -336,14 +371,23 @@ class OrderService extends BaseService {
})
)
return this.orderModel_.updateOne(
{
_id: orderId,
},
{
$set: updateFields,
}
)
return this.orderModel_
.updateOne(
{
_id: orderId,
},
{
$set: updateFields,
}
)
.then(result => {
// Notify subscribers
this.eventBus_.emit(OrderService.Events.UPDATED, result)
return result
})
.catch(err => {
throw new MedusaError(MedusaError.Types.DB_ERROR, err.message)
})
}
/**
+131 -58
View File
@@ -7,6 +7,11 @@ import { Validator, MedusaError } from "medusa-core-utils"
* @implements BaseService
*/
class ProductVariantService extends BaseService {
static Events = {
UPDATED: "product-variant.updated",
CREATED: "product-variant.created",
}
/** @param { productVariantModel: (ProductVariantModel) } */
constructor({ productVariantModel, eventBusService, regionService }) {
super()
@@ -73,6 +78,10 @@ class ProductVariantService extends BaseService {
...productVariant,
published: false,
})
.then(result => {
this.eventBus_.emit(ProductVariantService.Events.CREATED, result)
return result
})
.catch(err => {
throw new MedusaError(MedusaError.Types.DB_ERROR, err.message)
})
@@ -83,9 +92,13 @@ class ProductVariantService extends BaseService {
* @param {string} variantId - ID of the variant to publish.
* @return {Promise} resolves to the creation result.
*/
publish(variantId) {
async publish(variantId) {
return this.productVariantModel_
.updateOne({ _id: variantId }, { $set: { published: true } })
.then(result => {
this.eventBus_.emit(ProductVariantService.Events.UPDATED, result)
return result
})
.catch(err => {
throw new MedusaError(MedusaError.Types.DB_ERROR, err.message)
})
@@ -123,6 +136,10 @@ class ProductVariantService extends BaseService {
{ $set: update },
{ runValidators: true }
)
.then(result => {
this.eventBus_.emit(ProductVariantService.Events.UPDATED, result)
return result
})
.catch(err => {
throw new MedusaError(MedusaError.Types.DB_ERROR, err.message)
})
@@ -163,33 +180,49 @@ class ProductVariantService extends BaseService {
})
}
return this.productVariantModel_.updateOne(
return this.productVariantModel_
.updateOne(
{
_id: variant._id,
},
{
$set: {
prices: newPrices,
},
}
)
.then(result => {
this.eventBus_.emit(ProductVariantService.Events.UPDATED, result)
return result
})
.catch(err => {
throw new MedusaError(MedusaError.Types.DB_ERROR, err.message)
})
}
return this.productVariantModel_
.updateOne(
{
_id: variant._id,
},
{
$set: {
prices: newPrices,
prices: [
{
currency_code: currencyCode,
amount,
},
],
},
}
)
}
return this.productVariantModel_.updateOne(
{
_id: variant._id,
},
{
$set: {
prices: [
{
currency_code: currencyCode,
amount,
},
],
},
}
)
.then(result => {
this.eventBus_.emit(ProductVariantService.Events.UPDATED, result)
return result
})
.catch(err => {
throw new MedusaError(MedusaError.Types.DB_ERROR, err.message)
})
}
/**
@@ -262,39 +295,55 @@ class ProductVariantService extends BaseService {
})
}
return this.productVariantModel_.updateOne(
return this.productVariantModel_
.updateOne(
{
_id: variant._id,
},
{
$set: {
prices: newPrices,
},
}
)
.then(result => {
this.eventBus_.emit(ProductVariantService.Events.UPDATED, result)
return result
})
.catch(err => {
throw new MedusaError(MedusaError.Types.DB_ERROR, err.message)
})
}
// Set the price both for default currency price and for the region
return this.productVariantModel_
.updateOne(
{
_id: variant._id,
},
{
$set: {
prices: newPrices,
prices: [
{
region_id: region._id,
currency_code: region.currency_code,
amount,
},
{
currency_code: region.currency_code,
amount,
},
],
},
}
)
}
// Set the price both for default currency price and for the region
return this.productVariantModel_.updateOne(
{
_id: variant._id,
},
{
$set: {
prices: [
{
region_id: region._id,
currency_code: region.currency_code,
amount,
},
{
currency_code: region.currency_code,
amount,
},
],
},
}
)
.then(result => {
this.eventBus_.emit(ProductVariantService.Events.UPDATED, result)
return result
})
.catch(err => {
throw new MedusaError(MedusaError.Types.DB_ERROR, err.message)
})
}
/**
@@ -313,10 +362,18 @@ class ProductVariantService extends BaseService {
)
}
return this.productVariantModel_.updateOne(
{ _id: variantId, "options.option_id": optionId },
{ $set: { "options.$.value": `${optionValue}` } }
)
return this.productVariantModel_
.updateOne(
{ _id: variantId, "options.option_id": optionId },
{ $set: { "options.$.value": `${optionValue}` } }
)
.then(result => {
this.eventBus_.emit(ProductVariantService.Events.UPDATED, result)
return result
})
.catch(err => {
throw new MedusaError(MedusaError.Types.DB_ERROR, err.message)
})
}
/**
@@ -340,10 +397,18 @@ class ProductVariantService extends BaseService {
)
}
return this.productVariantModel_.updateOne(
{ _id: variant._id },
{ $push: { options: { option_id: optionId, value: `${optionValue}` } } }
)
return this.productVariantModel_
.updateOne(
{ _id: variant._id },
{ $push: { options: { option_id: optionId, value: `${optionValue}` } } }
)
.then(result => {
this.eventBus_.emit(ProductVariantService.Events.UPDATED, result)
return result
})
.catch(err => {
throw new MedusaError(MedusaError.Types.DB_ERROR, err.message)
})
}
/**
@@ -357,10 +422,18 @@ class ProductVariantService extends BaseService {
* @return {Promise} the result of the update operation.
*/
async deleteOptionValue(variantId, optionId) {
return this.productVariantModel_.updateOne(
{ _id: variantId },
{ $pull: { options: { option_id: optionId } } }
)
return this.productVariantModel_
.updateOne(
{ _id: variantId },
{ $pull: { options: { option_id: optionId } } }
)
.then(result => {
this.eventBus_.emit(ProductVariantService.Events.UPDATED, result)
return result
})
.catch(err => {
throw new MedusaError(MedusaError.Types.DB_ERROR, err.message)
})
}
/**
@@ -384,7 +457,7 @@ class ProductVariantService extends BaseService {
* @param {Object} selector - the query object for find
* @return {Promise} the result of the find operation
*/
list(selector) {
async list(selector) {
return this.productVariantModel_.find(selector)
}
+119 -50
View File
@@ -8,6 +8,11 @@ import { BaseService } from "medusa-interfaces"
* @implements BaseService
*/
class ProductService extends BaseService {
static Events = {
UPDATED: "product.updated",
CREATED: "product.created",
}
/** @param { productModel: (ProductModel) } */
constructor({ productModel, eventBusService, productVariantService }) {
super()
@@ -86,12 +91,16 @@ class ProductService extends BaseService {
* @param {object} product - the product to create
* @return {Promise} resolves to the creation result.
*/
createDraft(product) {
async createDraft(product) {
return this.productModel_
.create({
...product,
published: false,
})
.then(result => {
this.eventBus_.emit(ProductService.Events.CREATED, result)
return result
})
.catch(err => {
throw new MedusaError(MedusaError.Types.DB_ERROR, err.message)
})
@@ -102,9 +111,13 @@ class ProductService extends BaseService {
* @param {string} productId - ID of the product to publish.
* @return {Promise} resolves to the creation result.
*/
publish(productId) {
async publish(productId) {
return this.productModel_
.updateOne({ _id: productId }, { $set: { published: true } })
.then(result => {
this.eventBus_.emit(ProductService.Events.UPDATED, result)
return result
})
.catch(err => {
throw new MedusaError(MedusaError.Types.DB_ERROR, err.message)
})
@@ -183,6 +196,10 @@ class ProductService extends BaseService {
{ $set: update },
{ runValidators: true }
)
.then(result => {
this.eventBus_.emit(ProductService.Events.UPDATED, result)
return result
})
.catch(err => {
throw new MedusaError(MedusaError.Types.DB_ERROR, err.message)
})
@@ -266,10 +283,15 @@ class ProductService extends BaseService {
const newVariant = await this.productVariantService_.createDraft(variant)
return this.productModel_.updateOne(
{ _id: product._id },
{ $push: { variants: newVariant._id } }
)
return this.productModel_
.updateOne({ _id: product._id }, { $push: { variants: newVariant._id } })
.then(result => {
this.eventBus_.emit(ProductService.Events.UPDATED, result)
return result
})
.catch(err => {
throw new MedusaError(MedusaError.Types.DB_ERROR, err.message)
})
}
/**
@@ -303,7 +325,7 @@ class ProductService extends BaseService {
"Default Value"
)
)
).catch(err => {
).catch(async err => {
// If any of the variants failed to add the new option value we clean up
return Promise.all(
product.variants.map(async variantId =>
@@ -329,7 +351,11 @@ class ProductService extends BaseService {
},
}
)
.catch(err => {
.then(result => {
this.eventBus_.emit(ProductService.Events.UPDATED, result)
return result
})
.catch(async err => {
// If we failed to update the product clean up its variants
return Promise.all(
product.variants.map(async variantId =>
@@ -363,14 +389,22 @@ class ProductService extends BaseService {
return variant
})
return this.productModel_.updateOne(
{
_id: productId,
},
{
$set: { variants: newOrder },
}
)
return this.productModel_
.updateOne(
{
_id: productId,
},
{
$set: { variants: newOrder },
}
)
.then(result => {
this.eventBus_.emit(ProductService.Events.UPDATED, result)
return result
})
.catch(err => {
throw new MedusaError(MedusaError.Types.DB_ERROR, err.message)
})
}
/**
@@ -404,14 +438,22 @@ class ProductService extends BaseService {
return option
})
return this.productModel_.updateOne(
{
_id: productId,
},
{
$set: { options: newOrder },
}
)
return this.productModel_
.updateOne(
{
_id: productId,
},
{
$set: { options: newOrder },
}
)
.then(result => {
this.eventBus_.emit(ProductService.Events.UPDATED, result)
return result
})
.catch(err => {
throw new MedusaError(MedusaError.Types.DB_ERROR, err.message)
})
}
/**
@@ -448,15 +490,23 @@ class ProductService extends BaseService {
const update = {}
update["options.$.title"] = title
return this.productModel_.updateOne(
{
_id: productId,
"options._id": optionId,
},
{
$set: update,
}
)
return this.productModel_
.updateOne(
{
_id: productId,
"options._id": optionId,
},
{
$set: update,
}
)
.then(result => {
this.eventBus_.emit(ProductService.Events.UPDATED, result)
return result
})
.catch(err => {
throw new MedusaError(MedusaError.Types.DB_ERROR, err.message)
})
}
/**
@@ -504,16 +554,23 @@ class ProductService extends BaseService {
}
}
const result = await this.productModel_.updateOne(
{ _id: productId },
{
$pull: {
options: {
_id: optionId,
const result = await this.productModel_
.updateOne(
{ _id: productId },
{
$pull: {
options: {
_id: optionId,
},
},
},
}
)
}
)
.then(result => {
this.eventBus_.emit(ProductService.Events.UPDATED, result)
})
.catch(err => {
throw new MedusaError(MedusaError.Types.DB_ERROR, err.message)
})
// If we reached this point, we can delete option value from variants
if (product.variants) {
@@ -538,14 +595,22 @@ class ProductService extends BaseService {
await this.productVariantService_.delete(variantId)
return this.productModel_.updateOne(
{ _id: product._id },
{
$pull: {
variants: variantId,
},
}
)
return this.productModel_
.updateOne(
{ _id: product._id },
{
$pull: {
variants: variantId,
},
}
)
.then(result => {
this.eventBus_.emit(ProductService.Events.UPDATED, result)
return result
})
.catch(err => {
throw new MedusaError(MedusaError.Types.DB_ERROR, err.message)
})
}
async updateOptionValue(productId, variantId, optionId, value) {
@@ -638,6 +703,10 @@ class ProductService extends BaseService {
const keyPath = `metadata.${key}`
return this.productModel_
.updateOne({ _id: validatedId }, { $set: { [keyPath]: value } })
.then(result => {
this.eventBus_.emit(ProductService.Events.UPDATED, result)
return result
})
.catch(err => {
throw new MedusaError(MedusaError.Types.DB_ERROR, err.message)
})
@@ -37,7 +37,7 @@ class ShippingOptionService extends BaseService {
*/
validateId_(rawId) {
const schema = Validator.objectId()
const { value, error } = schema.validate(rawId)
const { value, error } = schema.validate(rawId.toString())
if (error) {
throw new MedusaError(
MedusaError.Types.INVALID_ARGUMENT,
@@ -246,10 +246,10 @@ class ShippingOptionService extends BaseService {
}
}
if (price.type === "flat_rate" && (!price.amount || price.amount < 0)) {
if (price.type === "flat_rate" && (!price.amount || price.amount <= 0)) {
throw new MedusaError(
MedusaError.Types.INVALID_DATA,
"Flat rate prices must have a postive amount field."
"Flat rate prices must have be zero or have a postive amount field."
)
}
@@ -33,7 +33,7 @@ class ShippingProfileService extends BaseService {
*/
validateId_(rawId) {
const schema = Validator.objectId()
const { value, error } = schema.validate(rawId)
const { value, error } = schema.validate(rawId.toString())
if (error) {
throw new MedusaError(
MedusaError.Types.INVALID_ARGUMENT,
@@ -76,6 +76,27 @@ class ShippingProfileService extends BaseService {
return profile
}
async retrieveDefault() {
return await this.profileModel_
.findOne({ name: "default_shipping_profile" })
.catch(err => {
throw new MedusaError(MedusaError.Types.DB_ERROR, err.message)
})
}
/**
* Creates a default shipping profile, if this does not already exist.
* @return {Promise<ShippingProfile>} the shipping profile
*/
async createDefault() {
const profile = await this.retrieveDefault()
if (!profile) {
return this.profileModel_.create({ name: "default_shipping_profile" })
}
return profile
}
/**
* Creates a new shipping profile.
* @param {ShippingProfile} profile - the shipping profile to create from
+1 -1
View File
@@ -28,7 +28,7 @@ class StoreService extends BaseService {
*/
validateId_(rawId) {
const schema = Validator.objectId()
const { value, error } = schema.validate(rawId)
const { value, error } = schema.validate(rawId.toString())
if (error) {
throw new MedusaError(
MedusaError.Types.INVALID_ARGUMENT,
+9
View File
@@ -9,6 +9,10 @@ import { BaseService } from "medusa-interfaces"
* @implements BaseService
*/
class UserService extends BaseService {
static Events = {
PASSWORD_RESET: "user.password_reset",
}
constructor({ userModel, eventBusService }) {
super()
@@ -245,6 +249,11 @@ class UserService extends BaseService {
const expiry = Math.floor(Date.now() / 1000) + 60 * 15
const payload = { user_id: user._id, exp: expiry }
const token = jwt.sign(payload, secret)
// Notify subscribers
this.eventBus_.emit(UserService.Events.PASSWORD_RESET, {
email: user.email,
token,
})
return token
}
+15
View File
@@ -4522,6 +4522,14 @@ 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@^0.3.0:
version "0.1.39"
resolved "https://registry.yarnpkg.com/medusa-core-utils/-/medusa-core-utils-0.1.39.tgz#d57816c9bd43f9a92883650c1e66add1665291df"
integrity sha512-R8+U1ile7if+nR6Cjh5exunx0ETV0OfkWUUBUpz1KmHSDv0V0CcvQqU9lcZesPFDEbu3Y2iEjsCqidVA4nG2nQ==
dependencies:
"@hapi/joi" "^16.1.8"
joi-objectid "^3.0.1"
medusa-interfaces@^0.1.27:
version "0.1.27"
resolved "https://registry.yarnpkg.com/medusa-interfaces/-/medusa-interfaces-0.1.27.tgz#e77f9a9f82a7118eac8b35c1498ef8a5cec78898"
@@ -4529,6 +4537,13 @@ medusa-interfaces@^0.1.27:
dependencies:
mongoose "^5.8.0"
medusa-test-utils@^0.3.0:
version "0.1.39"
resolved "https://registry.yarnpkg.com/medusa-test-utils/-/medusa-test-utils-0.1.39.tgz#b7c166006a2fa4f02e52ab3bfafc19a3ae787f3e"
integrity sha512-M/Br8/HYvl7x2oLnme4NxdQwoyV0XUyOWiCyvPp7q1HUTB684lhJf1MikZVrcSjsh2L1rpyi3GRbKdf4cpJWvw==
dependencies:
mongoose "^5.8.0"
memory-pager@^1.0.2:
version "1.5.0"
resolved "https://registry.yarnpkg.com/memory-pager/-/memory-pager-1.5.0.tgz#d8751655d22d384682741c972f2c3d6dfa3e66b5"