fix(medusa-plugin-addon): Fixes admin endpoints, Adds flag to avoid merging add-on line-items
This commit is contained in:
1 parent
34eb53f72e
commit
d8483cd135
14 files changed
+170
-89
No files matched your search
@@ -6,7 +6,7 @@ export default (rootDirectory) => {
|
|||||||
const app = Router()
|
const app = Router()
|
||||||
|
|
||||||
store(app, rootDirectory)
|
store(app, rootDirectory)
|
||||||
admin(app)
|
admin(app, rootDirectory)
|
||||||
|
|
||||||
return app
|
return app
|
||||||
}
|
}
|
||||||
@@ -5,11 +5,11 @@ export default async (req, res) => {
|
|||||||
name: Validator.string().required(),
|
name: Validator.string().required(),
|
||||||
prices: Validator.array()
|
prices: Validator.array()
|
||||||
.items({
|
.items({
|
||||||
currency_code: Validator.string().required(),
|
currency_code: Validator.string(),
|
||||||
amount: Validator.number().required(),
|
amount: Validator.number(),
|
||||||
})
|
})
|
||||||
.required(),
|
.required(),
|
||||||
valid_for: Validator.array().items(Validator.string()).required(),
|
valid_for: Validator.array().items(),
|
||||||
metadata: Validator.object().optional(),
|
metadata: Validator.object().optional(),
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -3,7 +3,13 @@ export default async (req, res) => {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
const addOnService = req.scope.resolve("addOnService")
|
const addOnService = req.scope.resolve("addOnService")
|
||||||
const addOn = await addOnService.retrieve(id)
|
let addOn = await addOnService.retrieve(id)
|
||||||
|
addOn = await addOnService.decorate(
|
||||||
|
addOn,
|
||||||
|
["name", "valid_for", "prices"],
|
||||||
|
["valid_for"]
|
||||||
|
)
|
||||||
|
|
||||||
res.json({ add_on: addOn })
|
res.json({ add_on: addOn })
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
throw err
|
throw err
|
||||||
|
|||||||
@@ -1,10 +1,24 @@
|
|||||||
import { Router } from "express"
|
import { Router } from "express"
|
||||||
import bodyParser from "body-parser"
|
import bodyParser from "body-parser"
|
||||||
|
import cors from "cors"
|
||||||
import middlewares from "../../middlewares"
|
import middlewares from "../../middlewares"
|
||||||
|
import { getConfigFile } from "medusa-core-utils"
|
||||||
|
|
||||||
const route = Router()
|
const route = Router()
|
||||||
|
|
||||||
export default (app) => {
|
export default (app, rootDirectory) => {
|
||||||
|
const { configModule } = getConfigFile(rootDirectory, `medusa-config`)
|
||||||
|
const config = (configModule && configModule.projectConfig) || {}
|
||||||
|
|
||||||
|
const adminCors = config.admin_cors || ""
|
||||||
|
|
||||||
|
route.use(
|
||||||
|
cors({
|
||||||
|
origin: adminCors.split(","),
|
||||||
|
credentials: true,
|
||||||
|
})
|
||||||
|
)
|
||||||
|
|
||||||
app.use("/admin", route)
|
app.use("/admin", route)
|
||||||
|
|
||||||
route.post(
|
route.post(
|
||||||
@@ -19,5 +33,23 @@ export default (app) => {
|
|||||||
middlewares.wrap(require("./update-add-on").default)
|
middlewares.wrap(require("./update-add-on").default)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
route.get(
|
||||||
|
"/add-ons",
|
||||||
|
bodyParser.json(),
|
||||||
|
middlewares.wrap(require("./list-add-ons").default)
|
||||||
|
)
|
||||||
|
|
||||||
|
route.get(
|
||||||
|
"/add-ons/:id",
|
||||||
|
bodyParser.json(),
|
||||||
|
middlewares.wrap(require("./get-add-on").default)
|
||||||
|
)
|
||||||
|
|
||||||
|
route.delete(
|
||||||
|
"/add-ons/:id",
|
||||||
|
bodyParser.json(),
|
||||||
|
middlewares.wrap(require("./delete-add-on").default)
|
||||||
|
)
|
||||||
|
|
||||||
return app
|
return app
|
||||||
}
|
}
|
||||||
@@ -1,10 +1,20 @@
|
|||||||
export default async (req, res) => {
|
export default async (req, res) => {
|
||||||
try {
|
try {
|
||||||
const addOnService = req.scope.resolve("addOnService")
|
const addOnService = req.scope.resolve("addOnService")
|
||||||
const addOns = await addOnService.list({})
|
let addOns = await addOnService.list({})
|
||||||
|
addOns = await Promise.all(
|
||||||
|
addOns.map((ao) =>
|
||||||
|
addOnService.decorate(
|
||||||
|
ao,
|
||||||
|
["name", "valid_for", "prices"],
|
||||||
|
["valid_for"]
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
res.status(200).json({ add_ons: addOns })
|
res.status(200).json({ add_ons: addOns })
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
console.log(err)
|
||||||
throw err
|
throw err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
import { Validator, MedusaError } from "medusa-core-utils";
|
import { Validator, MedusaError } from "medusa-core-utils"
|
||||||
|
|
||||||
export default async (req, res) => {
|
export default async (req, res) => {
|
||||||
const { id } = req.params;
|
const { id } = req.params
|
||||||
|
|
||||||
const schema = Validator.object().keys({
|
const schema = Validator.object().keys({
|
||||||
name: Validator.string().optional(),
|
name: Validator.string().optional(),
|
||||||
@@ -11,21 +11,29 @@ export default async (req, res) => {
|
|||||||
amount: Validator.number().required(),
|
amount: Validator.number().required(),
|
||||||
})
|
})
|
||||||
.optional(),
|
.optional(),
|
||||||
valid_for: Validator.array().items(Validator.string()).optional(),
|
valid_for: Validator.array().optional(),
|
||||||
metadata: Validator.object().optional(),
|
metadata: Validator.object().optional(),
|
||||||
});
|
})
|
||||||
|
|
||||||
const { value, error } = schema.validate(req.body);
|
const { value, error } = schema.validate(req.body)
|
||||||
if (error) {
|
if (error) {
|
||||||
throw new MedusaError(MedusaError.Types.INVALID_DATA, error.details);
|
throw new MedusaError(MedusaError.Types.INVALID_DATA, error.details)
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
const addOnService = req.scope.resolve("addOnService");
|
const addOnService = req.scope.resolve("addOnService")
|
||||||
|
|
||||||
const addOn = await addOnService.update(id, value);
|
if (value.metadata) {
|
||||||
|
Object.entries(value.metadata).map(([key, value]) => {
|
||||||
|
addOnService.setMetadata(id, key, value)
|
||||||
|
})
|
||||||
|
|
||||||
res.status(200).json({ addOn });
|
delete value.metadata
|
||||||
|
}
|
||||||
|
|
||||||
|
const addOn = await addOnService.update(id, value)
|
||||||
|
|
||||||
|
res.status(200).json({ addOn })
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
throw err;
|
throw err
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
|
||||||
@@ -30,6 +30,16 @@ export default async (req, res) => {
|
|||||||
cart = await cartService.addLineItem(cart._id, lineItem)
|
cart = await cartService.addLineItem(cart._id, lineItem)
|
||||||
cart = await cartService.decorate(cart, [], ["region"])
|
cart = await cartService.decorate(cart, [], ["region"])
|
||||||
|
|
||||||
|
cart.items = await Promise.all(
|
||||||
|
cart.items.map((item) =>
|
||||||
|
lineItemService.decorate(
|
||||||
|
item,
|
||||||
|
["title", "quantity", "thumbnail", "content", "should_merge"],
|
||||||
|
["add_ons"]
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
res.status(200).json({ cart })
|
res.status(200).json({ cart })
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
throw err
|
throw err
|
||||||
|
|||||||
@@ -13,7 +13,13 @@ export default async (req, res) => {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
const addOnService = req.scope.resolve("addOnService")
|
const addOnService = req.scope.resolve("addOnService")
|
||||||
const addOn = await addOnService.retrieveByProduct(value.product_id)
|
let addOn = await addOnService.retrieveByProduct(value.product_id)
|
||||||
|
addOn = await addOnService.decorate(
|
||||||
|
addOn,
|
||||||
|
["name", "valid_for", "prices"],
|
||||||
|
["valid_for"]
|
||||||
|
)
|
||||||
|
|
||||||
res.json({ add_on: addOn })
|
res.json({ add_on: addOn })
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
throw err
|
throw err
|
||||||
|
|||||||
@@ -43,6 +43,16 @@ export default async (req, res) => {
|
|||||||
|
|
||||||
cart = await cartService.decorate(cart, [], ["region"])
|
cart = await cartService.decorate(cart, [], ["region"])
|
||||||
|
|
||||||
|
cart.items = await Promise.all(
|
||||||
|
cart.items.map((item) =>
|
||||||
|
lineItemService.decorate(
|
||||||
|
item,
|
||||||
|
["title", "quantity", "thumbnail", "content", "should_merge"],
|
||||||
|
["add_ons"]
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
res.status(200).json({ cart })
|
res.status(200).json({ cart })
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
throw err
|
throw err
|
||||||
|
|||||||
@@ -33,56 +33,6 @@ class AddOnLineItemService extends BaseService {
|
|||||||
this.options_ = options
|
this.options_ = options
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Used to validate line items.
|
|
||||||
* @param {object} rawLineItem - the raw line item to validate.
|
|
||||||
* @return {object} the validated id
|
|
||||||
*/
|
|
||||||
validate(rawLineItem) {
|
|
||||||
const content = Validator.object({
|
|
||||||
unit_price: Validator.number().required(),
|
|
||||||
variant: Validator.object().required(),
|
|
||||||
product: Validator.object().required(),
|
|
||||||
quantity: Validator.number().integer().min(1).default(1),
|
|
||||||
})
|
|
||||||
|
|
||||||
const lineItemSchema = Validator.object({
|
|
||||||
title: Validator.string().required(),
|
|
||||||
is_giftcard: Validator.bool().optional(),
|
|
||||||
description: Validator.string().allow("").optional(),
|
|
||||||
thumbnail: Validator.string().allow("").optional(),
|
|
||||||
content: Validator.alternatives()
|
|
||||||
.try(content, Validator.array().items(content))
|
|
||||||
.required(),
|
|
||||||
quantity: Validator.number().integer().min(1).required(),
|
|
||||||
metadata: Validator.object().default({}),
|
|
||||||
})
|
|
||||||
|
|
||||||
const { value, error } = lineItemSchema.validate(rawLineItem)
|
|
||||||
if (error) {
|
|
||||||
throw new MedusaError(
|
|
||||||
MedusaError.Types.INVALID_DATA,
|
|
||||||
error.details[0].message
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
return value
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Contents of a line item
|
|
||||||
* @typedef {(object | array)} LineItemContent
|
|
||||||
* @property {number} unit_price - the price of the content
|
|
||||||
* @property {object} variant - the product variant of the content
|
|
||||||
* @property {object} product - the product of the content
|
|
||||||
* @property {number} quantity - the quantity of the content
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A collection of contents grouped in the same line item
|
|
||||||
* @typedef {LineItemContent[]} LineItemContentArray
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Generates a line item.
|
* Generates a line item.
|
||||||
* @param {string} variantId - id of the line item variant
|
* @param {string} variantId - id of the line item variant
|
||||||
@@ -90,7 +40,7 @@ class AddOnLineItemService extends BaseService {
|
|||||||
* @param {*} quantity - number of items
|
* @param {*} quantity - number of items
|
||||||
* @param {[string]} addOnIds - id of add-ons
|
* @param {[string]} addOnIds - id of add-ons
|
||||||
*/
|
*/
|
||||||
async generate(variantId, regionId, quantity, addOnIds) {
|
async generate(variantId, regionId, quantity, addOnIds, metadata = {}) {
|
||||||
const variant = await this.productVariantService_.retrieve(variantId)
|
const variant = await this.productVariantService_.retrieve(variantId)
|
||||||
const region = await this.regionService_.retrieve(regionId)
|
const region = await this.regionService_.retrieve(regionId)
|
||||||
|
|
||||||
@@ -132,13 +82,16 @@ class AddOnLineItemService extends BaseService {
|
|||||||
title: product.title,
|
title: product.title,
|
||||||
quantity,
|
quantity,
|
||||||
thumbnail: product.thumbnail,
|
thumbnail: product.thumbnail,
|
||||||
|
should_merge: false,
|
||||||
content: {
|
content: {
|
||||||
unit_price: unitPrice * quantity,
|
unit_price: unitPrice * quantity,
|
||||||
variant,
|
variant,
|
||||||
product,
|
product,
|
||||||
quantity: 1,
|
quantity: 1,
|
||||||
},
|
},
|
||||||
|
should_merge: false,
|
||||||
metadata: {
|
metadata: {
|
||||||
|
...metadata,
|
||||||
add_ons: addOnIds,
|
add_ons: addOnIds,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@@ -146,26 +99,17 @@ class AddOnLineItemService extends BaseService {
|
|||||||
return line
|
return line
|
||||||
}
|
}
|
||||||
|
|
||||||
isEqual(line, match) {
|
async decorate(lineItem, fields, expandFields = []) {
|
||||||
if (Array.isArray(line.content)) {
|
const requiredFields = ["_id", "metadata"]
|
||||||
if (
|
const decorated = _.pick(lineItem, fields.concat(requiredFields))
|
||||||
Array.isArray(match.content) &&
|
if (expandFields.includes("add_ons") && decorated.metadata.add_ons) {
|
||||||
match.content.length === line.content.length
|
decorated.metadata.add_ons = await Promise.all(
|
||||||
) {
|
decorated.metadata.add_ons.map(
|
||||||
return line.content.every(
|
async (ao) => await this.addOnService_.retrieve(ao)
|
||||||
(c, index) =>
|
)
|
||||||
c.variant._id.equals(match[index].variant._id) &&
|
|
||||||
c.quantity === match[index].quantity
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
} else if (!Array.isArray(match.content)) {
|
return decorated
|
||||||
return (
|
|
||||||
line.content.variant._id.equals(match.content.variant._id) &&
|
|
||||||
line.content.quantity === match.content.quantity
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
return false
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -197,6 +197,57 @@ class AddOnService extends BaseService {
|
|||||||
`A price for region: ${region.name} could not be found`
|
`A price for region: ${region.name} could not be found`
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Decorates a add-on with add-on variants.
|
||||||
|
* @param {AddOn} addOn - the add-on to decorate.
|
||||||
|
* @param {string[]} fields - the fields to include.
|
||||||
|
* @param {string[]} expandFields - fields to expand.
|
||||||
|
* @return {AddOn} return the decorated add-on.
|
||||||
|
*/
|
||||||
|
async decorate(addOn, fields, expandFields = []) {
|
||||||
|
const requiredFields = ["_id", "metadata"]
|
||||||
|
const decorated = _.pick(addOn, fields.concat(requiredFields))
|
||||||
|
if (expandFields.includes("valid_for")) {
|
||||||
|
decorated.valid_for = await Promise.all(
|
||||||
|
decorated.valid_for.map(
|
||||||
|
async (p) => await this.productService_.retrieve(p)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
return decorated
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Dedicated method to set metadata for an add-on.
|
||||||
|
* To ensure that plugins does not overwrite each
|
||||||
|
* others metadata fields, setMetadata is provided.
|
||||||
|
* @param {string} addOnId - the add-on to decorate.
|
||||||
|
* @param {string} key - key for metadata field
|
||||||
|
* @param {string} value - value for metadata field.
|
||||||
|
* @return {Promise} resolves to the updated result.
|
||||||
|
*/
|
||||||
|
async setMetadata(addOnId, key, value) {
|
||||||
|
const validatedId = this.validateId_(addOnId)
|
||||||
|
|
||||||
|
if (typeof key !== "string") {
|
||||||
|
throw new MedusaError(
|
||||||
|
MedusaError.Types.INVALID_ARGUMENT,
|
||||||
|
"Key type is invalid. Metadata keys must be strings"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const keyPath = `metadata.${key}`
|
||||||
|
return this.addOnModel_
|
||||||
|
.updateOne({ _id: validatedId }, { $set: { [keyPath]: value } })
|
||||||
|
.then((result) => {
|
||||||
|
this.eventBus_.emit(AddOnService.Events.UPDATED, result)
|
||||||
|
return result
|
||||||
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
throw new MedusaError(MedusaError.Types.DB_ERROR, err.message)
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default AddOnService
|
export default AddOnService
|
||||||
@@ -8,6 +8,7 @@ export default new mongoose.Schema({
|
|||||||
description: { type: String },
|
description: { type: String },
|
||||||
thumbnail: { type: String },
|
thumbnail: { type: String },
|
||||||
is_giftcard: { type: Boolean, default: false },
|
is_giftcard: { type: Boolean, default: false },
|
||||||
|
should_merge: { type: Boolean, default: true },
|
||||||
has_shipping: { type: Boolean, default: false },
|
has_shipping: { type: Boolean, default: false },
|
||||||
|
|
||||||
// mongoose doesn't allow multi-type validation but this field allows both
|
// mongoose doesn't allow multi-type validation but this field allows both
|
||||||
|
|||||||
@@ -375,7 +375,7 @@ class CartService extends BaseService {
|
|||||||
|
|
||||||
// If content matches one of the line items currently in the cart we can
|
// If content matches one of the line items currently in the cart we can
|
||||||
// simply update the quantity of the existing line item
|
// simply update the quantity of the existing line item
|
||||||
if (currentItem) {
|
if (currentItem && validatedLineItem.should_merge) {
|
||||||
const newQuantity = currentItem.quantity + validatedLineItem.quantity
|
const newQuantity = currentItem.quantity + validatedLineItem.quantity
|
||||||
|
|
||||||
// Confirm inventory
|
// Confirm inventory
|
||||||
|
|||||||
@@ -38,6 +38,7 @@ class LineItemService extends BaseService {
|
|||||||
const lineItemSchema = Validator.object({
|
const lineItemSchema = Validator.object({
|
||||||
title: Validator.string().required(),
|
title: Validator.string().required(),
|
||||||
is_giftcard: Validator.bool().optional(),
|
is_giftcard: Validator.bool().optional(),
|
||||||
|
should_merge: Validator.bool().optional(),
|
||||||
description: Validator.string()
|
description: Validator.string()
|
||||||
.allow("")
|
.allow("")
|
||||||
.optional(),
|
.optional(),
|
||||||
@@ -110,6 +111,7 @@ class LineItemService extends BaseService {
|
|||||||
title: product.title,
|
title: product.title,
|
||||||
description: variant.title,
|
description: variant.title,
|
||||||
quantity,
|
quantity,
|
||||||
|
should_merge: true,
|
||||||
thumbnail: product.thumbnail,
|
thumbnail: product.thumbnail,
|
||||||
content: {
|
content: {
|
||||||
unit_price,
|
unit_price,
|
||||||
@@ -117,6 +119,7 @@ class LineItemService extends BaseService {
|
|||||||
product,
|
product,
|
||||||
quantity: 1,
|
quantity: 1,
|
||||||
},
|
},
|
||||||
|
metadata,
|
||||||
}
|
}
|
||||||
|
|
||||||
if (product.is_giftcard) {
|
if (product.is_giftcard) {
|
||||||
|
|||||||
Reference in new issue
Block a user