Replaces MongoDB support with PostgreSQL (#151)

- All schemas have been rewritten to a relational model
- All services have been rewritten to accommodate the new data model
- Adds idempotency keys to core endpoints allowing you to retry requests with no additional side effects
- Adds staged jobs to avoid putting jobs in the queue when transactions abort
- Adds atomic transactions to all methods with access to the data layer

Co-authored-by: Oliver Windall Juhl <oliver@mrbltech.com>
This commit is contained in:
Sebastian RindomandOliver Windall Juhl committed 2021-01-26 10:26:14 +01:00
1 parent 5f819486fc
commit f1baca3cbd
499 files changed
+25909 -16128

No files matched your search

+1 -1
View File
@@ -7,6 +7,6 @@ if (process.env.NODE_ENV !== `test`) {
}
module.exports = {
presets: [["babel-preset-medusa-package"]],
presets: [["babel-preset-medusa-package"], ["@babel/preset-typescript"]],
ignore,
}
+5
View File
@@ -0,0 +1,5 @@
{
"cli": {
"migrationsDir": "src/migrations"
}
}
+16 -7
View File
@@ -1,6 +1,6 @@
{
"name": "@medusajs/medusa",
"version": "1.0.56",
"version": "1.0.33-alpha.205+8c87f25",
"description": "E-commerce for JAMstack",
"main": "dist/app.js",
"repository": {
@@ -18,12 +18,14 @@
"@babel/core": "^7.7.5",
"@babel/node": "^7.7.4",
"@babel/plugin-proposal-class-properties": "^7.7.4",
"@babel/plugin-proposal-decorators": "^7.12.1",
"@babel/plugin-transform-instanceof": "^7.8.3",
"@babel/plugin-transform-runtime": "^7.7.6",
"@babel/preset-env": "^7.7.5",
"@babel/preset-typescript": "^7.12.7",
"@babel/register": "^7.7.4",
"@babel/runtime": "^7.7.6",
"babel-preset-medusa-package": "^1.0.1",
"babel-preset-medusa-package": "^1.0.2-alpha.787+0646bd3",
"cross-env": "^5.2.1",
"eslint": "^6.8.0",
"jest": "^25.5.2",
@@ -33,9 +35,9 @@
},
"scripts": {
"start": "nodemon --watch plugins/ --watch src/ --exec babel-node src/app.js",
"watch": "babel -w src --out-dir dist/ --ignore **/__tests__",
"watch": "babel -w src --out-dir dist/ --ignore **/__tests__ --extensions \".ts,.js\"",
"prepare": "cross-env NODE_ENV=production npm run build",
"build": "babel src -d dist --ignore **/__tests__",
"build": "babel src -d dist --ignore **/__tests__ --extensions \".ts,.js\"",
"serve": "node dist/app.js",
"test": "jest"
},
@@ -60,21 +62,28 @@
"fs-exists-cached": "^1.0.0",
"glob": "^7.1.6",
"ioredis": "^4.17.3",
"joi": "^17.3.0",
"joi-objectid": "^3.0.1",
"jsonwebtoken": "^8.5.1",
"medusa-core-utils": "^1.0.11",
"medusa-test-utils": "^1.0.13",
"medusa-core-utils": "^1.0.12-alpha.787+0646bd3",
"medusa-test-utils": "^1.0.12-alpha.176+0646bd3",
"morgan": "^1.9.1",
"multer": "^1.4.2",
"passport": "^0.4.0",
"passport-http-bearer": "^1.0.1",
"passport-jwt": "^4.0.0",
"passport-local": "^1.0.0",
"pg": "^8.5.1",
"randomatic": "^3.1.1",
"redis": "^3.0.2",
"reflect-metadata": "^0.1.13",
"request-ip": "^2.1.3",
"resolve-cwd": "^3.0.0",
"scrypt-kdf": "^2.0.1",
"typeorm": "^0.2.29",
"ulid": "^2.3.0",
"uuid": "^8.3.1",
"winston": "^3.2.1"
},
"gitHead": "b54e28769a423c9285a1119535cfa1590d08a559"
"gitHead": "8c87f25f766154368b0f15424028550966f7d3d6"
}
@@ -5,6 +5,8 @@ export default () => {
const logger = req.scope.resolve("logger")
logger.error(err.message)
console.log(err)
let statusCode = 500
switch (err.name) {
case MedusaError.Types.NOT_ALLOWED:
@@ -22,7 +22,7 @@ export default async (req, res) => {
}
// Add JWT to cookie
req.session.jwt = jwt.sign({ userId: result.user._id }, config.jwtSecret, {
req.session.jwt = jwt.sign({ userId: result.user.id }, config.jwtSecret, {
expiresIn: "24h",
})
@@ -18,8 +18,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, ["_id", "email"])
res.status(201).json({ customer: data })
res.status(201).json({ customer })
} catch (err) {
throw err
}
@@ -1,28 +1,10 @@
export default async (req, res) => {
const { id } = req.params
try {
const orderService = req.scope.resolve("orderService")
const customerService = req.scope.resolve("customerService")
let customer = await customerService.retrieve(id)
customer = await customerService.decorate(
customer,
[
"email",
"payment_methods",
"has_account",
"shipping_addresses",
"phone",
],
[]
)
customer.orders = await orderService.list({ customer_id: customer._id })
customer.orders = await Promise.all(
customer.orders.map(order => {
return orderService.decorate(order, ["total", "payment_status"], [])
})
)
const customer = await customerService.retrieve(id, {
relations: ["orders"],
})
res.json({ customer })
} catch (err) {
@@ -1,22 +1,19 @@
export default async (req, res) => {
try {
const customerService = req.scope.resolve("customerService")
const queryBuilderService = req.scope.resolve("queryBuilderService")
const query = queryBuilderService.buildQuery(req.query, [
"email",
"first_name",
"last_name",
])
const limit = parseInt(req.query.limit) || 0
const limit = parseInt(req.query.limit) || 10
const offset = parseInt(req.query.offset) || 0
const customers = await customerService.list(query, offset, limit)
const listConfig = {
relations: [],
skip: offset,
take: limit,
}
const numCustomers = await customerService.count()
const customers = await customerService.list({}, listConfig)
res.json({ customers, total_count: numCustomers })
res.json({ customers, count: customers.length, offset, limit })
} catch (error) {
throw error
}
@@ -20,15 +20,10 @@ export default async (req, res) => {
const customerService = req.scope.resolve("customerService")
await customerService.update(id, value)
const customer = await customerService.retrieve(id)
const data = await customerService.decorate(customer)
data.orders = await Promise.all(
customer.orders.map(async oId => {
const order = await orderService.retrieve(oId)
return orderService.decorate(order, [], [])
})
)
res.status(200).json({ customer: data })
const customer = await customerService.retrieve(id, {
relations: ["orders"],
})
res.status(200).json({ customer })
} catch (err) {
throw err
}
@@ -29,7 +29,23 @@ describe("POST /admin/discounts/:discount_id/regions/:region_id", () => {
it("calls service retrieve", () => {
expect(DiscountServiceMock.retrieve).toHaveBeenCalledTimes(1)
expect(DiscountServiceMock.retrieve).toHaveBeenCalledWith(
IdMap.getId("total10")
IdMap.getId("total10"),
{
select: [
"id",
"code",
"is_dynamic",
"rule_id",
"parent_discount_id",
"starts_at",
"ends_at",
"created_at",
"updated_at",
"deleted_at",
"metadata",
],
relations: ["rule", "parent_discount", "regions", "rule.valid_for"],
}
)
expect(DiscountServiceMock.addRegion).toHaveBeenCalledTimes(1)
@@ -9,7 +9,7 @@ describe("POST /admin/discounts/:discount_id/variants/:variant_id", () => {
beforeAll(async () => {
subject = await request(
"POST",
`/admin/discounts/${IdMap.getId("total10")}/variants/${IdMap.getId(
`/admin/discounts/${IdMap.getId("total10")}/products/${IdMap.getId(
"testVariant"
)}`,
{
@@ -29,11 +29,27 @@ describe("POST /admin/discounts/:discount_id/variants/:variant_id", () => {
it("calls service retrieve", () => {
expect(DiscountServiceMock.retrieve).toHaveBeenCalledTimes(1)
expect(DiscountServiceMock.retrieve).toHaveBeenCalledWith(
IdMap.getId("total10")
IdMap.getId("total10"),
{
select: [
"id",
"code",
"is_dynamic",
"rule_id",
"parent_discount_id",
"starts_at",
"ends_at",
"created_at",
"updated_at",
"deleted_at",
"metadata",
],
relations: ["rule", "parent_discount", "regions", "rule.valid_for"],
}
)
expect(DiscountServiceMock.addValidVariant).toHaveBeenCalledTimes(1)
expect(DiscountServiceMock.addValidVariant).toHaveBeenCalledWith(
expect(DiscountServiceMock.addValidProduct).toHaveBeenCalledTimes(1)
expect(DiscountServiceMock.addValidProduct).toHaveBeenCalledWith(
IdMap.getId("total10"),
IdMap.getId("testVariant")
)
@@ -10,7 +10,7 @@ describe("POST /admin/discounts", () => {
subject = await request("POST", "/admin/discounts", {
payload: {
code: "TEST",
discount_rule: {
rule: {
type: "fixed",
value: 10,
allocation: "total",
@@ -32,12 +32,13 @@ describe("POST /admin/discounts", () => {
expect(DiscountServiceMock.create).toHaveBeenCalledTimes(1)
expect(DiscountServiceMock.create).toHaveBeenCalledWith({
code: "TEST",
discount_rule: {
rule: {
type: "fixed",
value: 10,
allocation: "total",
},
is_dynamic: false
is_disabled: false,
is_dynamic: false,
})
})
})
@@ -49,7 +50,7 @@ describe("POST /admin/discounts", () => {
subject = await request("POST", "/admin/discounts", {
payload: {
code: "10%OFF",
discount_rule: {
rule: {
value: 10,
allocation: "total",
},
@@ -67,9 +68,7 @@ describe("POST /admin/discounts", () => {
})
it("returns error", () => {
expect(subject.body.message[0].message).toEqual(
`"discount_rule.type" is required`
)
expect(subject.body.message[0].message).toEqual(`"rule.type" is required`)
})
})
})
@@ -2,6 +2,27 @@ import { IdMap } from "medusa-test-utils"
import { request } from "../../../../../helpers/test-request"
import { DiscountServiceMock } from "../../../../../services/__mocks__/discount"
const defaultFields = [
"id",
"code",
"is_dynamic",
"rule_id",
"parent_discount_id",
"starts_at",
"ends_at",
"created_at",
"updated_at",
"deleted_at",
"metadata",
]
const defaultRelations = [
"rule",
"parent_discount",
"regions",
"rule.valid_for",
]
describe("GET /admin/discounts/:discount_id", () => {
describe("successful retrieval", () => {
let subject
@@ -27,7 +48,11 @@ describe("GET /admin/discounts/:discount_id", () => {
it("calls service retrieve", () => {
expect(DiscountServiceMock.retrieve).toHaveBeenCalledTimes(1)
expect(DiscountServiceMock.retrieve).toHaveBeenCalledWith(
IdMap.getId("total10")
IdMap.getId("total10"),
{
select: defaultFields,
relations: defaultRelations,
}
)
})
})
@@ -23,33 +23,6 @@ describe("GET /admin/discounts", () => {
it("calls service retrieve", () => {
expect(DiscountServiceMock.list).toHaveBeenCalledTimes(1)
expect(DiscountServiceMock.list).toHaveBeenCalledWith({})
})
})
describe("is_giftcard filter", () => {
let subject
beforeAll(async () => {
jest.clearAllMocks()
subject = await request("GET", `/admin/discounts?is_giftcard=true`, {
adminSession: {
jwt: {
userId: IdMap.getId("admin_user"),
},
},
})
})
it("returns 200", () => {
expect(subject.status).toEqual(200)
})
it("calls service retrieve", () => {
expect(DiscountServiceMock.list).toHaveBeenCalledTimes(1)
expect(DiscountServiceMock.list).toHaveBeenCalledWith({
is_giftcard: true,
})
})
})
@@ -72,12 +45,7 @@ describe("GET /admin/discounts", () => {
})
it("calls service retrieve", () => {
expect(DiscountServiceMock.decorate).toHaveBeenCalledTimes(1)
expect(
DiscountServiceMock.decorate
).toHaveBeenCalledWith(expect.anything(), expect.anything(), ["regions"])
expect(DiscountServiceMock.list).toHaveBeenCalledTimes(1)
expect(DiscountServiceMock.list).toHaveBeenCalledWith({})
})
})
})
@@ -2,6 +2,27 @@ import { IdMap } from "medusa-test-utils"
import { request } from "../../../../../helpers/test-request"
import { DiscountServiceMock } from "../../../../../services/__mocks__/discount"
const defaultFields = [
"id",
"code",
"is_dynamic",
"rule_id",
"parent_discount_id",
"starts_at",
"ends_at",
"created_at",
"updated_at",
"deleted_at",
"metadata",
]
const defaultRelations = [
"rule",
"parent_discount",
"regions",
"rule.valid_for",
]
describe("DELETE /admin/discounts/:discount_id/regions/region_id", () => {
describe("successful removal", () => {
let subject
@@ -29,7 +50,11 @@ describe("DELETE /admin/discounts/:discount_id/regions/region_id", () => {
it("calls service retrieve", () => {
expect(DiscountServiceMock.retrieve).toHaveBeenCalledTimes(1)
expect(DiscountServiceMock.retrieve).toHaveBeenCalledWith(
IdMap.getId("total10")
IdMap.getId("total10"),
{
select: defaultFields,
relations: defaultRelations,
}
)
expect(DiscountServiceMock.removeRegion).toHaveBeenCalledTimes(1)
@@ -2,14 +2,35 @@ import { IdMap } from "medusa-test-utils"
import { request } from "../../../../../helpers/test-request"
import { DiscountServiceMock } from "../../../../../services/__mocks__/discount"
describe("DELETE /admin/discounts/:discount_id/variants/:variant_id", () => {
const defaultFields = [
"id",
"code",
"is_dynamic",
"rule_id",
"parent_discount_id",
"starts_at",
"ends_at",
"created_at",
"updated_at",
"deleted_at",
"metadata",
]
const defaultRelations = [
"rule",
"parent_discount",
"regions",
"rule.valid_for",
]
describe("DELETE /admin/discounts/:discount_id/products/:variant_id", () => {
describe("successful addition", () => {
let subject
beforeAll(async () => {
subject = await request(
"DELETE",
`/admin/discounts/${IdMap.getId("total10")}/variants/${IdMap.getId(
`/admin/discounts/${IdMap.getId("total10")}/products/${IdMap.getId(
"testVariant"
)}`,
{
@@ -29,11 +50,15 @@ describe("DELETE /admin/discounts/:discount_id/variants/:variant_id", () => {
it("calls service retrieve", () => {
expect(DiscountServiceMock.retrieve).toHaveBeenCalledTimes(1)
expect(DiscountServiceMock.retrieve).toHaveBeenCalledWith(
IdMap.getId("total10")
IdMap.getId("total10"),
{
select: defaultFields,
relations: defaultRelations,
}
)
expect(DiscountServiceMock.removeValidVariant).toHaveBeenCalledTimes(1)
expect(DiscountServiceMock.removeValidVariant).toHaveBeenCalledWith(
expect(DiscountServiceMock.removeValidProduct).toHaveBeenCalledTimes(1)
expect(DiscountServiceMock.removeValidProduct).toHaveBeenCalledWith(
IdMap.getId("total10"),
IdMap.getId("testVariant")
)
@@ -13,7 +13,8 @@ describe("POST /admin/discounts", () => {
{
payload: {
code: "10TOTALOFF",
discount_rule: {
rule: {
id: "1234",
type: "fixed",
value: 10,
allocation: "total",
@@ -38,7 +39,8 @@ describe("POST /admin/discounts", () => {
IdMap.getId("total10"),
{
code: "10TOTALOFF",
discount_rule: {
rule: {
id: "1234",
type: "fixed",
value: 10,
allocation: "total",
@@ -1,4 +1,4 @@
import { MedusaError, Validator } from "medusa-core-utils"
import { defaultFields, defaultRelations } from "./"
export default async (req, res) => {
const { discount_id, region_id } = req.params
@@ -7,8 +7,12 @@ export default async (req, res) => {
await discountService.addRegion(discount_id, region_id)
const data = discountService.retrieve(discount_id)
res.status(200).json({ discount: data })
const discount = await discountService.retrieve(discount_id, {
select: defaultFields,
relations: defaultRelations,
})
res.status(200).json({ discount })
} catch (err) {
throw err
}
@@ -0,0 +1,20 @@
import { defaultFields, defaultRelations } from "./"
export default async (req, res) => {
const { discount_id, variant_id } = req.params
try {
const discountService = req.scope.resolve("discountService")
await discountService.addValidProduct(discount_id, variant_id)
const discount = await discountService.retrieve(discount_id, {
select: defaultFields,
relations: defaultRelations,
})
res.status(200).json({ discount })
} catch (err) {
throw err
}
}
@@ -1,15 +0,0 @@
import { MedusaError, Validator } from "medusa-core-utils"
export default async (req, res) => {
const { discount_id, variant_id } = req.params
try {
const discountService = req.scope.resolve("discountService")
await discountService.addValidVariant(discount_id, variant_id)
const data = discountService.retrieve(discount_id)
res.status(200).json({ discount: data })
} catch (err) {
throw err
}
}
@@ -4,7 +4,7 @@ export default async (req, res) => {
const schema = Validator.object().keys({
code: Validator.string().required(),
is_dynamic: Validator.boolean().default(false),
discount_rule: Validator.object()
rule: Validator.object()
.keys({
description: Validator.string().optional(),
type: Validator.string().required(),
@@ -14,11 +14,9 @@ export default async (req, res) => {
allocation: Validator.string().required(),
valid_for: Validator.array().items(Validator.string()),
usage_limit: Validator.number().optional(),
total_limit: Validator.number().optional(),
})
.required(),
usage_count: Validator.number().optional(),
disabled: Validator.boolean().optional(),
is_disabled: Validator.boolean().default(false),
starts_at: Validator.date().optional(),
ends_at: Validator.date().optional(),
regions: Validator.array()
@@ -34,9 +32,15 @@ export default async (req, res) => {
try {
const discountService = req.scope.resolve("discountService")
const data = await discountService.create(value)
res.status(200).json({ discount: data })
const created = await discountService.create(value)
const discount = await discountService.retrieve(created.id, [
"rule",
"rule.valid_for",
"regions",
])
res.status(200).json({ discount })
} catch (err) {
throw err
}
@@ -2,6 +2,7 @@ import { MedusaError, Validator } from "medusa-core-utils"
export default async (req, res) => {
const { discount_id } = req.params
const schema = Validator.object().keys({
code: Validator.string().required(),
metadata: Validator.object().optional(),
@@ -16,9 +17,11 @@ export default async (req, res) => {
const discountService = req.scope.resolve("discountService")
await discountService.createDynamicCode(discount_id, value)
const data = await discountService.retrieve(dicsount_id)
const discount = await discountService.retrieve(discount_id, {
relations: ["rule", "rule.valid_for", "regions"],
})
res.status(200).json({ discount: data })
res.status(200).json({ discount })
} catch (err) {
throw err
}
@@ -1,5 +1,6 @@
export default async (req, res) => {
const { discount_id } = req.params
try {
const discountService = req.scope.resolve("discountService")
await discountService.delete(discount_id)
@@ -1,20 +1,15 @@
import { MedusaError, Validator } from "medusa-core-utils"
export default async (req, res) => {
const { discount_id, code } = req.params
const { value, error } = schema.validate(req.body)
if (error) {
throw new MedusaError(MedusaError.Types.INVALID_DATA, error.details)
}
try {
const discountService = req.scope.resolve("discountService")
await discountService.deleteDynamicCode(discount_id, code)
const data = await discountService.retrieve(dicsount_id)
const discount = await discountService.retrieve(discount_id, {
relations: ["rule", "rule.valid_for", "regions"],
})
res.status(200).json({ discount: data })
res.status(200).json({ discount })
} catch (err) {
throw err
}
@@ -1,25 +1,15 @@
import { defaultFields, defaultRelations } from "./"
export default async (req, res) => {
const { discount_id } = req.params
try {
const discountService = req.scope.resolve("discountService")
const data = await discountService.retrieve(discount_id)
const data = await discountService.retrieve(discount_id, {
select: defaultFields,
relations: defaultRelations,
})
const discount = await discountService.decorate(
data,
[
"code",
"is_dynamic",
"discount_rule",
"usage_count",
"disabled",
"starts_at",
"ends_at",
"regions",
],
["valid_for"]
)
res.status(200).json({ discount })
res.status(200).json({ discount: data })
} catch (err) {
throw err
}
@@ -34,12 +34,12 @@ export default app => {
// Discount valid variants management
route.post(
"/:discount_id/variants/:variant_id",
middlewares.wrap(require("./add-valid-variant").default)
"/:discount_id/products/:variant_id",
middlewares.wrap(require("./add-valid-product").default)
)
route.delete(
"/:discount_id/variants/:variant_id",
middlewares.wrap(require("./remove-valid-variant").default)
"/:discount_id/products/:variant_id",
middlewares.wrap(require("./remove-valid-product").default)
)
// Discount region management
@@ -54,3 +54,24 @@ export default app => {
return app
}
export const defaultFields = [
"id",
"code",
"is_dynamic",
"rule_id",
"parent_discount_id",
"starts_at",
"ends_at",
"created_at",
"updated_at",
"deleted_at",
"metadata",
]
export const defaultRelations = [
"rule",
"parent_discount",
"regions",
"rule.valid_for",
]
@@ -1,36 +1,24 @@
import { defaultFields, defaultRelations } from "./"
export default async (req, res) => {
try {
const selector = {}
const limit = parseInt(req.query.limit) || 20
const offset = parseInt(req.query.offset) || 0
const discountService = req.scope.resolve("discountService")
if ("is_giftcard" in req.query) {
selector.is_giftcard = req.query.is_giftcard === "true"
const listConfig = {
select: defaultFields,
relations: defaultRelations,
skip: offset,
take: limit,
}
let expandFields = []
if ("expand_fields" in req.query) {
expandFields = req.query.expand_fields.split(",")
}
const discounts = await discountService.list(selector, listConfig)
let includeFields = [
"usage_count",
"starts_at",
"ends_at",
"original_amount",
"created",
]
if ("fields" in req.query) {
includeFields = req.query.fields.split(",")
}
const raw = await discountService.list(selector)
const data = await Promise.all(
raw.map(d => discountService.decorate(d, includeFields, expandFields))
)
res.status(200).json({ discounts: data })
res.status(200).json({ discounts })
} catch (err) {
throw err
}
@@ -1,3 +1,5 @@
import { defaultFields, defaultRelations } from "./"
export default async (req, res) => {
const { discount_id, region_id } = req.params
@@ -5,9 +7,12 @@ export default async (req, res) => {
const discountService = req.scope.resolve("discountService")
await discountService.removeRegion(discount_id, region_id)
const discount = await discountService.retrieve(discount_id, {
select: defaultFields,
relations: defaultRelations,
})
const data = discountService.retrieve(discount_id)
res.status(200).json({ discounts: data })
res.status(200).json({ discount })
} catch (err) {
throw err
}
@@ -0,0 +1,20 @@
import { defaultFields, defaultRelations } from "./"
export default async (req, res) => {
const { discount_id, variant_id } = req.params
try {
const discountService = req.scope.resolve("discountService")
await discountService.removeValidProduct(discount_id, variant_id)
const discount = await discountService.retrieve(discount_id, {
select: defaultFields,
relations: defaultRelations,
})
res.status(200).json({ discount })
} catch (err) {
throw err
}
}
@@ -1,14 +0,0 @@
export default async (req, res) => {
const { discount_id, variant_id } = req.params
try {
const discountService = req.scope.resolve("discountService")
await discountService.removeValidVariant(discount_id, variant_id)
const data = discountService.retrieve(discount_id)
res.status(200).json({ discounts: data })
} catch (err) {
throw err
}
}
@@ -1,23 +1,22 @@
import { MedusaError, Validator } from "medusa-core-utils"
import { defaultFields, defaultRelations } from "./"
export default async (req, res) => {
const { discount_id } = req.params
const schema = Validator.object().keys({
code: Validator.string().optional(),
is_dynamic: Validator.boolean().default(false),
is_giftcard: Validator.boolean().optional(),
discount_rule: Validator.object()
rule: Validator.object()
.keys({
id: Validator.string().required(),
description: Validator.string().optional(),
type: Validator.string().required(),
value: Validator.number().required(),
allocation: Validator.string().required(),
valid_for: Validator.array().items(Validator.string()),
usage_limit: Validator.number().optional(),
})
.optional(),
usage_count: Validator.number().optional(),
disabled: Validator.boolean().optional(),
is_disabled: Validator.boolean().optional(),
starts_at: Validator.date().optional(),
ends_at: Validator.date().optional(),
regions: Validator.array()
@@ -35,9 +34,12 @@ export default async (req, res) => {
await discountService.update(discount_id, value)
const data = await discountService.retrieve(discount_id)
const discount = await discountService.retrieve(discount_id, {
select: defaultFields,
relations: defaultRelations,
})
res.status(200).json({ discounts: data })
res.status(200).json({ discount })
} catch (err) {
throw err
}
@@ -0,0 +1,34 @@
import { MedusaError, Validator } from "medusa-core-utils"
import { defaultFields, defaultRelations } from "./"
export default async (req, res) => {
const schema = Validator.object().keys({
value: Validator.number()
.integer()
.optional(),
ends_at: Validator.date().optional(),
is_disabled: Validator.boolean().optional(),
region_id: Validator.string().optional(),
metadata: Validator.object().optional(),
})
const { value, error } = schema.validate(req.body)
if (error) {
throw new MedusaError(MedusaError.Types.INVALID_DATA, error.details)
}
try {
const giftCardService = req.scope.resolve("giftCardService")
await giftCardService.create(value)
const giftCard = await giftCardService.retrieve(id, {
select: defaultFields,
relations: defaultRelations,
})
res.status(200).json({ gift_card: giftCard })
} catch (err) {
throw err
}
}
@@ -0,0 +1,16 @@
export default async (req, res) => {
const { id } = req.params
try {
const giftCardService = req.scope.resolve("giftCardService")
await giftCardService.delete(id)
res.json({
id,
object: "gift-card",
deleted: true,
})
} catch (err) {
throw err
}
}
@@ -0,0 +1,17 @@
import { defaultFields, defaultRelations } from "./"
export default async (req, res) => {
const { id } = req.params
try {
const giftCardService = req.scope.resolve("giftCardService")
const giftCard = await giftCardService.retrieve(id, {
select: defaultFields,
relations: defaultRelations,
})
res.status(200).json({ gift_card: giftCard })
} catch (err) {
throw err
}
}
@@ -0,0 +1,52 @@
import { Router } from "express"
import middlewares from "../../../middlewares"
const route = Router()
export default app => {
app.use("/gift-cards", route)
route.get("/", middlewares.wrap(require("./list-gift-cards").default))
route.post("/", middlewares.wrap(require("./create-gift-card").default))
route.get("/:id", middlewares.wrap(require("./get-gift-card").default))
route.post("/:id", middlewares.wrap(require("./update-gift-card").default))
route.delete("/:id", middlewares.wrap(require("./delete-gift-card").default))
return app
}
export const defaultFields = [
"id",
"code",
"value",
"balance",
"region_id",
"is_disabled",
"ends_at",
"created_at",
"updated_at",
"deleted_at",
"metadata",
]
export const defaultRelations = ["region"]
export const allowedFields = [
"id",
"code",
"value",
"balance",
"region_id",
"is_disabled",
"ends_at",
"created_at",
"updated_at",
"deleted_at",
"metadata",
]
export const allowedRelations = ["region"]
@@ -0,0 +1,19 @@
import { defaultFields, defaultRelations } from "./"
export default async (req, res) => {
try {
const selector = {}
const giftCardService = req.scope.resolve("giftCardService")
const giftCards = await giftCardService.list(selector, {
select: defaultFields,
relations: defaultRelations,
order: { created_at: "DESC" },
})
res.status(200).json({ gift_cards: giftCards })
} catch (err) {
throw err
}
}
@@ -0,0 +1,35 @@
import { MedusaError, Validator } from "medusa-core-utils"
export default async (req, res) => {
const { id } = req.params
const schema = Validator.object().keys({
balance: Validator.number()
.precision(0)
.optional(),
ends_at: Validator.date().optional(),
is_disabled: Validator.boolean().optional(),
region_id: Validator.string().optional(),
metadata: Validator.object().optional(),
})
const { value, error } = schema.validate(req.body)
if (error) {
throw new MedusaError(MedusaError.Types.INVALID_DATA, error.details)
}
try {
const giftCardService = req.scope.resolve("giftCardService")
await giftCardService.update(id, value)
const giftCard = await giftCardService.retrieve(id, {
select: defaultFields,
relations: defaultRelations,
})
res.status(200).json({ gift_card: giftCard })
} catch (err) {
throw err
}
}
@@ -9,11 +9,15 @@ import regionRoutes from "./regions"
import shippingOptionRoutes from "./shipping-options"
import shippingProfileRoutes from "./shipping-profiles"
import discountRoutes from "./discounts"
import giftCardRoutes from "./gift-cards"
import orderRoutes from "./orders"
import storeRoutes from "./store"
import uploadRoutes from "./uploads"
import customerRoutes from "./customers"
import appRoutes from "./apps"
import swapRoutes from "./swaps"
import returnRoutes from "./returns"
import variantRoutes from "./variants"
const route = Router()
@@ -48,10 +52,14 @@ export default (app, container, config) => {
shippingOptionRoutes(route)
shippingProfileRoutes(route)
discountRoutes(route)
giftCardRoutes(route)
orderRoutes(route)
storeRoutes(route)
uploadRoutes(route)
customerRoutes(route)
swapRoutes(route)
returnRoutes(route)
variantRoutes(route)
return app
}
@@ -33,7 +33,7 @@ describe("POST /admin/orders/:id/archive", () => {
it("returns order with status = archived", () => {
expect(subject.status).toEqual(200)
expect(subject.body.order._id).toEqual(IdMap.getId("processed-order"))
expect(subject.body.order.id).toEqual(IdMap.getId("processed-order"))
expect(subject.body.order.status).toEqual("archived")
})
})
@@ -33,7 +33,7 @@ describe("POST /admin/orders/:id/cancel", () => {
it("returns order with status = cancelled", () => {
expect(subject.status).toEqual(200)
expect(subject.body.order._id).toEqual(IdMap.getId("test-order"))
expect(subject.body.order.id).toEqual(IdMap.getId("test-order"))
expect(subject.body.order.status).toEqual("cancelled")
})
})
@@ -33,7 +33,7 @@ describe("POST /admin/orders/:id/capture", () => {
it("returns order with payment_status = captured", () => {
expect(subject.status).toEqual(200)
expect(subject.body.order._id).toEqual(IdMap.getId("test-order"))
expect(subject.body.order.id).toEqual(IdMap.getId("test-order"))
expect(subject.body.order.payment_status).toEqual("captured")
})
})
@@ -48,7 +48,7 @@ describe("POST /admin/orders/:id/fulfillment", () => {
it("returns order with fulfillment_status = fulfilled", () => {
expect(subject.status).toEqual(200)
expect(subject.body.order._id).toEqual(IdMap.getId("test-order"))
expect(subject.body.order.id).toEqual(IdMap.getId("test-order"))
expect(subject.body.order.fulfillment_status).toEqual("fulfilled")
})
})
@@ -2,6 +2,53 @@ import { IdMap } from "medusa-test-utils"
import { request } from "../../../../../helpers/test-request"
import { OrderServiceMock } from "../../../../../services/__mocks__/order"
const defaultRelations = [
"customer",
"billing_address",
"shipping_address",
"discounts",
"shipping_methods",
"payments",
"fulfillments",
"returns",
"gift_cards",
"gift_card_transactions",
"swaps",
"swaps.return_order",
"swaps.payment",
"swaps.shipping_methods",
"swaps.shipping_address",
"swaps.additional_items",
"swaps.fulfillments",
]
const defaultFields = [
"id",
"status",
"fulfillment_status",
"payment_status",
"display_id",
"cart_id",
"customer_id",
"email",
"region_id",
"currency_code",
"tax_rate",
"canceled_at",
"created_at",
"updated_at",
"metadata",
"items.refundable",
"shipping_total",
"discount_total",
"tax_total",
"refunded_total",
"gift_card_total",
"subtotal",
"total",
"refundable_amount",
]
describe("GET /admin/orders", () => {
describe("successfully gets an order", () => {
let subject
@@ -27,13 +74,17 @@ describe("GET /admin/orders", () => {
it("calls orderService retrieve", () => {
expect(OrderServiceMock.retrieve).toHaveBeenCalledTimes(1)
expect(OrderServiceMock.retrieve).toHaveBeenCalledWith(
IdMap.getId("test-order")
IdMap.getId("test-order"),
{
select: defaultFields,
relations: defaultRelations,
}
)
})
it("returns order", () => {
expect(subject.status).toEqual(200)
expect(subject.body.order._id).toEqual(IdMap.getId("test-order"))
expect(subject.body.order.id).toEqual(IdMap.getId("test-order"))
})
})
})
@@ -1,6 +1,7 @@
import { IdMap } from "medusa-test-utils"
import { request } from "../../../../../helpers/test-request"
import { OrderServiceMock } from "../../../../../services/__mocks__/order"
import { orders } from "../../../../../services/__mocks__/order"
import { ReturnService } from "../../../../../services/__mocks__/return"
describe("POST /admin/orders/:id/return", () => {
describe("successfully returns full order", () => {
@@ -19,6 +20,7 @@ describe("POST /admin/orders/:id/return", () => {
quantity: 10,
},
],
refund: 10,
},
adminSession: {
jwt: {
@@ -34,17 +36,21 @@ describe("POST /admin/orders/:id/return", () => {
})
it("calls OrderService return", () => {
expect(OrderServiceMock.requestReturn).toHaveBeenCalledTimes(1)
expect(OrderServiceMock.requestReturn).toHaveBeenCalledWith(
IdMap.getId("test-order"),
[
{
item_id: IdMap.getId("existingLine"),
quantity: 10,
},
],
undefined, // no shipping method
undefined // no refund amount
expect(ReturnService.create).toHaveBeenCalledTimes(1)
expect(ReturnService.create).toHaveBeenCalledWith(
{
order_id: IdMap.getId("test-order"),
idempotency_key: "testkey",
items: [
{
item_id: IdMap.getId("existingLine"),
quantity: 10,
},
],
refund_amount: 10,
shipping_method: undefined,
},
orders.testOrder
)
})
})
@@ -81,18 +87,134 @@ describe("POST /admin/orders/:id/return", () => {
})
it("calls OrderService return", () => {
expect(OrderServiceMock.requestReturn).toHaveBeenCalledTimes(1)
expect(OrderServiceMock.requestReturn).toHaveBeenCalledWith(
IdMap.getId("test-order"),
[
{
item_id: IdMap.getId("existingLine"),
quantity: 10,
},
],
undefined, // no shipping method
0
expect(ReturnService.create).toHaveBeenCalledTimes(1)
expect(ReturnService.create).toHaveBeenCalledWith(
{
order_id: IdMap.getId("test-order"),
idempotency_key: "testkey",
items: [
{
item_id: IdMap.getId("existingLine"),
quantity: 10,
},
],
refund_amount: 0,
shipping_method: undefined,
},
orders.testOrder
)
})
})
describe("defaults to 0 on negative refund amount", () => {
let subject
beforeAll(async () => {
jest.clearAllMocks()
subject = await request(
"POST",
`/admin/orders/${IdMap.getId("test-order")}/return`,
{
payload: {
items: [
{
item_id: IdMap.getId("existingLine"),
quantity: 10,
},
],
refund: -1,
},
adminSession: {
jwt: {
userId: IdMap.getId("admin_user"),
},
},
}
)
})
it("returns 200", () => {
expect(subject.status).toEqual(200)
})
it("calls OrderService return", () => {
expect(ReturnService.create).toHaveBeenCalledTimes(1)
expect(ReturnService.create).toHaveBeenCalledWith(
{
order_id: IdMap.getId("test-order"),
idempotency_key: "testkey",
items: [
{
item_id: IdMap.getId("existingLine"),
quantity: 10,
},
],
refund_amount: 0,
shipping_method: undefined,
},
orders.testOrder
)
})
})
describe("fulfills", () => {
let subject
beforeAll(async () => {
jest.clearAllMocks()
subject = await request(
"POST",
`/admin/orders/${IdMap.getId("test-order")}/return`,
{
payload: {
items: [
{
item_id: IdMap.getId("existingLine"),
quantity: 10,
},
],
refund: 100,
return_shipping: {
option_id: "opt_1234",
price: 12,
},
},
adminSession: {
jwt: {
userId: IdMap.getId("admin_user"),
},
},
}
)
})
it("returns 200", () => {
expect(subject.status).toEqual(200)
})
it("calls OrderService return", () => {
expect(ReturnService.create).toHaveBeenCalledTimes(1)
expect(ReturnService.create).toHaveBeenCalledWith(
{
order_id: IdMap.getId("test-order"),
idempotency_key: "testkey",
items: [
{
item_id: IdMap.getId("existingLine"),
quantity: 10,
},
],
refund_amount: 100,
shipping_method: {
option_id: "opt_1234",
price: 12,
},
},
orders.testOrder
)
expect(ReturnService.fulfill).toHaveBeenCalledTimes(1)
expect(ReturnService.fulfill).toHaveBeenCalledWith("return")
})
})
})
@@ -1,40 +0,0 @@
import { IdMap } from "medusa-test-utils"
import { request } from "../../../../../helpers/test-request"
import { OrderServiceMock } from "../../../../../services/__mocks__/order"
describe("POST /admin/orders/:id/metadata", () => {
describe("successfully sets metadata on order", () => {
let subject
beforeAll(async () => {
subject = await request(
"POST",
`/admin/orders/${IdMap.getId("test-order")}/metadata`,
{
payload: {
key: "Test key",
value: "Test value",
},
adminSession: {
jwt: {
userId: IdMap.getId("admin_user"),
},
},
}
)
})
it("returns 200", () => {
expect(subject.status).toEqual(200)
})
it("calls OrderService setMetadata", () => {
expect(OrderServiceMock.setMetadata).toHaveBeenCalledTimes(1)
expect(OrderServiceMock.setMetadata).toHaveBeenCalledWith(
IdMap.getId("test-order"),
"Test key",
"Test value"
)
})
})
})
@@ -0,0 +1,41 @@
import _ from "lodash"
import { Validator, MedusaError } from "medusa-core-utils"
import { defaultFields, defaultRelations } from "./"
export default async (req, res) => {
const { id } = req.params
const schema = Validator.object().keys({
price: Validator.number()
.integer()
.integer()
.allow(0)
.required(),
option_id: 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 orderService = req.scope.resolve("orderService")
await orderService.addShippingMethod(id, value.option_id, value.data, {
price: value.price,
})
const order = await orderService.retrieve(id, {
select: defaultFields,
relations: defaultRelations,
})
res.status(200).json({ order })
} catch (err) {
throw err
}
}
@@ -3,12 +3,13 @@ export default async (req, res) => {
try {
const orderService = req.scope.resolve("orderService")
let order = await orderService.archive(id)
order = await orderService.decorate(
order,
[],
["region", "customer", "swaps"]
)
await orderService.archive(id)
const order = await orderService.retrieve(id, {
relations: ["region", "customer", "swaps"],
})
res.json({ order })
} catch (error) {
throw error
@@ -3,12 +3,13 @@ export default async (req, res) => {
try {
const orderService = req.scope.resolve("orderService")
let order = await orderService.cancel(id)
order = await orderService.decorate(
order,
[],
["region", "customer", "swaps"]
)
await orderService.cancel(id)
const order = await orderService.retrieve(id, {
relations: ["region", "customer", "swaps"],
})
res.json({ order })
} catch (error) {
throw error
@@ -1,14 +1,18 @@
import { defaultRelations, defaultFields } from "./"
export default async (req, res) => {
const { id } = req.params
try {
const orderService = req.scope.resolve("orderService")
let order = await orderService.capturePayment(id)
order = await orderService.decorate(
order,
[],
["region", "customer", "swaps"]
)
await orderService.capturePayment(id)
const order = await orderService.retrieve(id, {
select: defaultFields,
relations: defaultRelations,
})
res.json({ order })
} catch (error) {
throw error
@@ -3,12 +3,13 @@ export default async (req, res) => {
try {
const orderService = req.scope.resolve("orderService")
let order = await orderService.completeOrder(id)
order = await orderService.decorate(
order,
[],
["region", "customer", "swaps"]
)
await orderService.completeOrder(id)
const order = await orderService.retrieve(id, {
relations: ["region", "customer", "swaps"],
})
res.json({ order })
} catch (error) {
throw error
@@ -1,4 +1,5 @@
import { MedusaError, Validator } from "medusa-core-utils"
import { defaultRelations, defaultFields } from "./"
export default async (req, res) => {
const { id } = req.params
@@ -21,19 +22,14 @@ export default async (req, res) => {
try {
const orderService = req.scope.resolve("orderService")
let order = await orderService.createFulfillment(
id,
value.items,
value.metadata
)
await orderService.createFulfillment(id, value.items, value.metadata)
const data = await orderService.decorate(
order,
[],
["region", "customer", "swaps"]
)
const order = await orderService.retrieve(id, {
select: defaultFields,
relations: defaultRelations,
})
res.json({ order: data })
res.json({ order })
} catch (error) {
throw error
}
@@ -1,4 +1,5 @@
import { MedusaError, Validator } from "medusa-core-utils"
import { defaultRelations, defaultFields } from "./"
export default async (req, res) => {
const { id } = req.params
@@ -17,16 +18,18 @@ export default async (req, res) => {
try {
const orderService = req.scope.resolve("orderService")
let order = await orderService.createShipment(
await orderService.createShipment(
id,
value.fulfillment_id,
value.tracking_numbers
)
order = await orderService.decorate(
order,
[],
["region", "customer", "swaps"]
)
const order = await orderService.retrieve(id, {
select: defaultFields,
relations: defaultRelations,
})
res.json({ order })
} catch (error) {
throw error
@@ -1,4 +1,5 @@
import { MedusaError, Validator } from "medusa-core-utils"
import { defaultFields, defaultRelations } from "./"
export default async (req, res) => {
const { id, swap_id } = req.params
@@ -19,22 +20,18 @@ export default async (req, res) => {
const orderService = req.scope.resolve("orderService")
const swapService = req.scope.resolve("swapService")
const order = await orderService.retrieve(id)
await swapService.createShipment(
swap_id,
value.fulfillment_id,
value.tracking_numbers
)
// Decorate the order
const data = await orderService.decorate(
order,
[],
["region", "customer", "swaps"]
)
const order = await orderService.retrieve(id, {
select: defaultFields,
relations: defaultRelations,
})
res.json({ order: data })
res.json({ order })
} catch (error) {
throw error
}
@@ -1,4 +1,5 @@
import { MedusaError, Validator } from "medusa-core-utils"
import { defaultFields, defaultRelations } from "./"
export default async (req, res) => {
const { id } = req.params
@@ -12,8 +13,10 @@ export default async (req, res) => {
.required(),
return_shipping: Validator.object()
.keys({
id: Validator.string().optional(),
price: Validator.number().optional(),
option_id: Validator.string().optional(),
price: Validator.number()
.integer()
.optional(),
})
.optional(),
additional_items: Validator.array().items({
@@ -27,41 +30,141 @@ export default async (req, res) => {
throw new MedusaError(MedusaError.Types.INVALID_DATA, error.details)
}
const idempotencyKeyService = req.scope.resolve("idempotencyKeyService")
const headerKey = req.get("Idempotency-Key") || ""
let idempotencyKey
try {
idempotencyKey = await idempotencyKeyService.initializeRequest(
headerKey,
req.method,
req.params,
req.path
)
} catch (error) {
res.status(409).send("Failed to create idempotency key")
return
}
res.setHeader("Access-Control-Expose-Headers", "Idempotency-Key")
res.setHeader("Idempotency-Key", idempotencyKey.idempotency_key)
try {
const orderService = req.scope.resolve("orderService")
const swapService = req.scope.resolve("swapService")
const returnService = req.scope.resolve("returnService")
let order = await orderService.retrieve(id)
let inProgress = true
let err = false
// Phase 1: Create swap and add it to the order
const swap = await swapService.create(
order,
value.return_items,
value.additional_items,
value.return_shipping
)
while (inProgress) {
switch (idempotencyKey.recovery_point) {
case "started": {
const { key, error } = await idempotencyKeyService.workStage(
idempotencyKey.idempotency_key,
async manager => {
const order = await orderService
.withTransaction(manager)
.retrieve(id, {
select: ["refunded_total", "total"],
relations: ["items", "swaps"],
})
await orderService.registerSwapCreated(id, swap._id)
const swap = await swapService
.withTransaction(manager)
.create(
order,
value.return_items,
value.additional_items,
value.return_shipping,
{ idempotency_key: idempotencyKey.idempotency_key }
)
// --> swap_created
// Phase 2: Create a return request from the swap
await swapService.requestReturn(order, swap._id)
await swapService.withTransaction(manager).createCart(swap.id)
const returnOrder = await returnService
.withTransaction(manager)
.retrieveBySwap(swap.id)
// --> return_request_created
// Phase 3: Create a cart that can be used to pay for the swap difference
await swapService.createCart(order, swap._id)
await returnService
.withTransaction(manager)
.fulfill(returnOrder.id)
// --> finished
return {
recovery_point: "swap_created",
}
}
)
order = await orderService.retrieve(id)
const data = await orderService.decorate(
order,
[],
["region", "customer", "swaps"]
)
if (error) {
inProgress = false
err = error
} else {
idempotencyKey = key
}
break
}
res.status(200).json({ order: data })
} catch (err) {
throw err
case "swap_created": {
const { key, error } = await idempotencyKeyService.workStage(
idempotencyKey.idempotency_key,
async manager => {
const swaps = await swapService.list({
idempotency_key: idempotencyKey.idempotency_key,
})
if (!swaps.length) {
throw new MedusaError(
MedusaError.Types.INVALID_DATA,
"Swap not found"
)
}
const order = await orderService.retrieve(id, {
select: defaultFields,
relations: defaultRelations,
})
return {
response_code: 200,
response_body: { order },
}
}
)
if (error) {
inProgress = false
err = error
} else {
idempotencyKey = key
}
break
}
case "finished": {
inProgress = false
break
}
default:
idempotencyKey = await idempotencyKeyService.update(
idempotencyKey.idempotency_key,
{
recovery_point: "finished",
response_code: 500,
response_body: { message: "Unknown recovery point" },
}
)
break
}
}
if (err) {
throw err
}
res.status(idempotencyKey.response_code).json(idempotencyKey.response_body)
} catch (error) {
throw error
}
}
@@ -3,8 +3,12 @@ export default async (req, res) => {
try {
const orderService = req.scope.resolve("orderService")
let order = await orderService.deleteMetadata(id, key)
order = await orderService.decorate(order, [], ["region"])
await orderService.deleteMetadata(id, key)
const order = await orderService.retrieve(id, {
relations: ["region", "customer", "swaps"],
})
res.status(200).json({ order })
} catch (err) {
@@ -1,4 +1,5 @@
import { MedusaError, Validator } from "medusa-core-utils"
import { defaultRelations, defaultFields } from "./"
export default async (req, res) => {
const { id, swap_id } = req.params
@@ -15,21 +16,20 @@ export default async (req, res) => {
try {
const orderService = req.scope.resolve("orderService")
const swapService = req.scope.resolve("swapService")
const entityManager = req.scope.resolve("manager")
// Fetch the order
const order = await orderService.retrieve(id)
await entityManager.transaction(async manager => {
await swapService
.withTransaction(manager)
.createFulfillment(swap_id, value.metadata)
// Receive the return
await swapService.createFulfillment(order, swap_id, value.metadata)
const order = await orderService.withTransaction(manager).retrieve(id, {
select: defaultFields,
relations: defaultRelations,
})
// Decorate the order
const data = await orderService.decorate(
order,
[],
["region", "customer", "swaps"]
)
res.status(200).json({ order: data })
res.status(200).json({ order })
})
} catch (err) {
throw err
}
@@ -1,16 +1,15 @@
import { defaultRelations, defaultFields } from "./"
export default async (req, res) => {
const { id } = req.params
try {
const orderService = req.scope.resolve("orderService")
const customerService = req.scope.resolve("customerService")
let order = await orderService.retrieve(id)
order = await orderService.decorate(
order,
[],
["region", "customer", "swaps"]
)
const order = await orderService.retrieve(id, {
select: defaultFields,
relations: defaultRelations,
})
res.json({ order })
} catch (error) {
@@ -87,6 +87,14 @@ export default app => {
*/
route.post("/:id/cancel", middlewares.wrap(require("./cancel-order").default))
/**
* Add a shipping method
*/
route.post(
"/:id/shipping-methods",
middlewares.wrap(require("./add-shipping-method").default)
)
/**
* Archive an order.
*/
@@ -132,14 +140,6 @@ export default app => {
middlewares.wrap(require("./process-swap-payment").default)
)
/**
* Set metadata key / value pair.
*/
route.post(
"/:id/metadata",
middlewares.wrap(require("./set-metadata").default)
)
/**
* Delete metadata key / value pair.
*/
@@ -150,3 +150,91 @@ export default app => {
return app
}
export const defaultRelations = [
"customer",
"billing_address",
"shipping_address",
"discounts",
"shipping_methods",
"payments",
"fulfillments",
"returns",
"gift_cards",
"gift_card_transactions",
"swaps",
"swaps.return_order",
"swaps.payment",
"swaps.shipping_methods",
"swaps.shipping_address",
"swaps.additional_items",
"swaps.fulfillments",
]
export const defaultFields = [
"id",
"status",
"fulfillment_status",
"payment_status",
"display_id",
"cart_id",
"customer_id",
"email",
"region_id",
"currency_code",
"tax_rate",
"canceled_at",
"created_at",
"updated_at",
"metadata",
"items.refundable",
"shipping_total",
"discount_total",
"tax_total",
"refunded_total",
"gift_card_total",
"subtotal",
"total",
"refundable_amount",
]
export const allowedFields = [
"id",
"status",
"fulfillment_status",
"payment_status",
"display_id",
"cart_id",
"customer_id",
"email",
"region_id",
"currency_code",
"tax_rate",
"canceled_at",
"created_at",
"updated_at",
"metadata",
"shipping_total",
"discount_total",
"tax_total",
"refunded_total",
"subtotal",
"gift_card_total",
"total",
"refundable_amount",
]
export const allowedRelations = [
"customer",
"region",
"billing_address",
"shipping_address",
"discounts",
"shipping_methods",
"payments",
"fulfillments",
"returns",
"swaps",
"swaps.return_order",
"swaps.additional_items",
]
@@ -1,80 +1,61 @@
import _ from "lodash"
import { Not } from "typeorm"
import { defaultRelations, defaultFields } from "./"
export default async (req, res) => {
try {
const orderService = req.scope.resolve("orderService")
const queryBuilderService = req.scope.resolve("queryBuilderService")
let query = queryBuilderService.buildQuery(req.query, [
"display_id",
"email",
"status",
"fulfillment_status",
"payment_status",
])
const limit = parseInt(req.query.limit) || 50
const offset = parseInt(req.query.offset) || 0
let orders
let selector = {}
// Temporary solution for admin tabs filtering
// Will be replaced in Mongo -> Postgres migration
if (req.query["status"]) {
if (req.query["status"] === "returns") {
query = {
"returns.status": "requested",
}
}
if (req.query["status"] === "new") {
query = {
$or: [
{ fulfillment_status: { $ne: "shipped" } },
{ payment_status: { $ne: "captured" } },
],
}
}
if (req.query["status"] === "requires_action") {
query = {
$or: [
{ status: "requires_action" },
{ payment_status: "requires_action" },
{ fulfillment_status: "requires_action" },
],
}
}
if (req.query["status"] === "swaps") {
const swapService = req.scope.resolve("swapService")
const swapsInProgress = await swapService.list({
fulfillment_status: { $ne: "shipped" },
})
if (swapsInProgress.length) {
query = {
swaps: { $in: swapsInProgress.map(s => s._id.toString()) },
}
}
}
if ("q" in req.query) {
selector.q = req.query.q
}
orders = await orderService.list(query, offset, limit)
let includeFields = []
if ("fields" in req.query) {
includeFields = req.query.fields.split(",")
}
orders = await Promise.all(
orders.map(order => orderService.decorate(order, includeFields))
let expandFields = []
if ("expand" in req.query) {
expandFields = req.query.expand.split(",")
}
if ("new" in req.query) {
selector = {
payment_status: Not("captured"),
fulfillment_status: Not("shipped"),
}
}
if ("requires_more" in req.query) {
selector = {
payment_status: Not("captured"),
fulfillment_status: Not("shipped"),
}
}
const listConfig = {
select: includeFields.length ? includeFields : defaultFields,
relations: expandFields.length ? expandFields : defaultRelations,
skip: offset,
take: limit,
order: { created_at: "DESC" },
}
const [orders, count] = await orderService.listAndCount(
selector,
listConfig
)
let numOrders = await orderService.count()
const fields = [...includeFields, ...expandFields]
const data = orders.map(o => _.pick(o, fields))
res.json({ orders, total_count: numOrders })
res.json({ orders: data, count, offset, limit })
} catch (error) {
throw error
}
@@ -1,22 +1,22 @@
import { defaultFields, defaultRelations } from "./"
export default async (req, res) => {
const { id, swap_id } = req.params
try {
const orderService = req.scope.resolve("orderService")
const swapService = req.scope.resolve("swapService")
const entityManager = req.scope.resolve("manager")
const order = await orderService.retrieve(id)
await entityManager.transaction(async manager => {
await swapService.withTransaction(manager).processDifference(swap_id)
await swapService.processDifference(swap_id)
const order = await orderService.withTransaction(manager).retrieve(id, {
select: defaultFields,
relations: defaultRelations,
})
// Decorate the order
const data = await orderService.decorate(
order,
[],
["region", "customer", "swaps"]
)
res.json({ order: data })
res.json({ order })
})
} catch (error) {
throw error
}
@@ -1,4 +1,5 @@
import { MedusaError, Validator } from "medusa-core-utils"
import { defaultRelations, defaultFields } from "./"
export default async (req, res) => {
const { id, return_id } = req.params
@@ -10,7 +11,9 @@ export default async (req, res) => {
quantity: Validator.number().required(),
})
.required(),
refund: Validator.number().optional(),
refund: Validator.number()
.integer()
.optional(),
})
const { value, error } = schema.validate(req.body)
@@ -22,9 +25,11 @@ export default async (req, res) => {
const orderService = req.scope.resolve("orderService")
let refundAmount = value.refund
if (typeof value.refund !== "undefined" && value.refund < 0) {
refundAmount = 0
}
let order = await orderService.receiveReturn(
id,
return_id,
@@ -32,11 +37,11 @@ export default async (req, res) => {
refundAmount,
true
)
order = await orderService.decorate(
order,
[],
["region", "customer", "swaps"]
)
order = await orderService.retrieve(id, {
select: defaultFields,
relations: defaultRelations,
})
res.status(200).json({ order })
} catch (err) {
@@ -1,4 +1,5 @@
import { MedusaError, Validator } from "medusa-core-utils"
import { defaultFields, defaultRelations } from "./"
export default async (req, res) => {
const { id, swap_id } = req.params
@@ -20,24 +21,24 @@ export default async (req, res) => {
try {
const orderService = req.scope.resolve("orderService")
const swapService = req.scope.resolve("swapService")
const entityManager = req.scope.resolve("manager")
// Fetch the order
let order = await orderService.retrieve(id)
await entityManager.transaction(async manager => {
await swapService
.withTransaction(manager)
.receiveReturn(swap_id, value.items)
// Receive the return
await swapService.receiveReturn(order, swap_id, value.items)
await orderService
.withTransaction(manager)
.registerSwapReceived(id, swap_id)
})
// Register swap reception
order = await orderService.registerSwapReceived(id, swap_id)
const order = await orderService.retrieve(id, {
select: defaultFields,
relations: defaultRelations,
})
// Decorate the order
const data = await orderService.decorate(
order,
[],
["region", "customer", "swaps"]
)
res.status(200).json({ order: data })
res.status(200).json({ order })
} catch (err) {
throw err
}
@@ -1,9 +1,12 @@
import { MedusaError, Validator } from "medusa-core-utils"
import { defaultRelations, defaultFields } from "./"
export default async (req, res) => {
const { id } = req.params
const schema = Validator.object().keys({
amount: Validator.number().required(),
amount: Validator.number()
.integer()
.required(),
reason: Validator.string().required(),
note: Validator.string()
.allow("")
@@ -17,17 +20,13 @@ export default async (req, res) => {
try {
const orderService = req.scope.resolve("orderService")
let order = await orderService.createRefund(
id,
value.amount,
value.reason,
value.note
)
order = await orderService.decorate(
order,
[],
["region", "customer", "swaps"]
)
await orderService.createRefund(id, value.amount, value.reason, value.note)
const order = await orderService.retrieve(id, {
select: defaultFields,
relations: defaultRelations,
})
res.status(200).json({ order })
} catch (err) {
@@ -1,4 +1,5 @@
import { MedusaError, Validator } from "medusa-core-utils"
import { defaultRelations, defaultFields } from "./"
export default async (req, res) => {
const { id } = req.params
@@ -10,10 +11,18 @@ export default async (req, res) => {
quantity: Validator.number().required(),
})
.required(),
shipping_method: Validator.string().optional(),
shipping_price: Validator.number().optional(),
return_shipping: Validator.object()
.keys({
option_id: Validator.string().optional(),
price: Validator.number()
.integer()
.optional(),
})
.optional(),
receive_now: Validator.boolean().default(false),
refund: Validator.number().optional(),
refund: Validator.number()
.integer()
.optional(),
})
const { value, error } = schema.validate(req.body)
@@ -21,59 +30,166 @@ export default async (req, res) => {
throw new MedusaError(MedusaError.Types.INVALID_DATA, error.details)
}
const idempotencyKeyService = req.scope.resolve("idempotencyKeyService")
const headerKey = req.get("Idempotency-Key") || ""
let idempotencyKey
try {
idempotencyKey = await idempotencyKeyService.initializeRequest(
headerKey,
req.method,
req.params,
req.path
)
} catch (error) {
res.status(409).send("Failed to create idempotency key")
return
}
res.setHeader("Access-Control-Expose-Headers", "Idempotency-Key")
res.setHeader("Idempotency-Key", idempotencyKey.idempotency_key)
try {
const orderService = req.scope.resolve("orderService")
const returnService = req.scope.resolve("returnService")
let oldOrder
let existingReturns = []
if (value.receive_now) {
oldOrder = await orderService.retrieve(id)
existingReturns = oldOrder.returns.map(r => r._id)
}
let inProgress = true
let err = false
let shippingMethod
if (value.shipping_method) {
shippingMethod = {
id: value.shipping_method,
price: value.shipping_price,
while (inProgress) {
switch (idempotencyKey.recovery_point) {
case "started": {
const { key, error } = await idempotencyKeyService.workStage(
idempotencyKey.idempotency_key,
async manager => {
const order = await orderService
.withTransaction(manager)
.retrieve(id, {
select: ["refunded_total", "total"],
relations: ["items"],
})
const returnObj = {
order_id: id,
idempotency_key: idempotencyKey.idempotency_key,
items: value.items,
}
if (value.return_shipping) {
returnObj.shipping_method = value.return_shipping
}
if (typeof value.refund !== "undefined" && value.refund < 0) {
returnObj.refund_amount = 0
} else {
if (value.refund) {
returnObj.refund_amount = value.refund
}
}
const createdReturn = await returnService
.withTransaction(manager)
.create(returnObj, order)
if (value.return_shipping) {
await returnService
.withTransaction(manager)
.fulfill(createdReturn.id)
}
return {
recovery_point: "return_requested",
}
}
)
if (error) {
inProgress = false
err = error
} else {
idempotencyKey = key
}
break
}
case "return_requested": {
const { key, error } = await idempotencyKeyService.workStage(
idempotencyKey.idempotency_key,
async manager => {
let order = await orderService
.withTransaction(manager)
.retrieve(id, { relations: ["returns"] })
/**
* If we are ready to receive immediately, we find the newly created return
* and register it as received.
*/
if (value.receive_now) {
let ret = await returnService.withTransaction(manager).list({
idempotency_key: idempotencyKey.idempotency_key,
})
if (!ret.length) {
throw new MedusaError(
MedusaError.Types.INVALID_DATA,
`Return not found`
)
}
ret = ret[0]
order = await returnService
.withTransaction(manager)
.receiveReturn(order.id, ret.id, value.items, value.refund)
}
order = await orderService.withTransaction(manager).retrieve(id, {
select: defaultFields,
relations: defaultRelations,
})
return {
response_code: 200,
response_body: { order },
}
}
)
if (error) {
inProgress = false
err = error
} else {
idempotencyKey = key
}
break
}
case "finished": {
inProgress = false
break
}
default:
idempotencyKey = await idempotencyKeyService.update(
idempotencyKey.idempotency_key,
{
recovery_point: "finished",
response_code: 500,
response_body: { message: "Unknown recovery point" },
}
)
break
}
}
let refundAmount = value.refund
if (typeof value.refund !== "undefined" && value.refund < 0) {
refundAmount = 0
}
let order = await orderService.requestReturn(
id,
value.items,
shippingMethod,
refundAmount
)
/**
* If we are ready to receive immediately, we find the newly created return
* and register it as received.
*/
if (value.receive_now) {
const newReturn = order.returns.find(
r => !existingReturns.includes(r._id)
)
order = await orderService.return(
id,
newReturn._id,
value.items,
value.refund
)
if (err) {
throw err
}
order = await orderService.decorate(
order,
[],
["region", "customer", "swaps"]
)
res.status(200).json({ order })
res.status(idempotencyKey.response_code).json(idempotencyKey.response_body)
} catch (err) {
console.log(err)
throw err
}
}
@@ -1,12 +1,13 @@
import { MedusaError, Validator } from "medusa-core-utils"
import { defaultFields, defaultRelations } from "."
export default async (req, res) => {
const { id } = req.params
const schema = Validator.object().keys({
email: Validator.string().email(),
billing_address: Validator.address(),
shipping_address: Validator.address(),
billing_address: Validator.object(),
shipping_address: Validator.object(),
items: Validator.array(),
region: Validator.string(),
discounts: Validator.array(),
@@ -31,12 +32,13 @@ export default async (req, res) => {
try {
const orderService = req.scope.resolve("orderService")
let order = await orderService.update(id, value)
order = await orderService.decorate(
order,
[],
["region", "customer", "swaps"]
)
await orderService.update(id, value)
const order = await orderService.retrieve(id, {
select: defaultFields,
relations: defaultRelations,
})
res.status(200).json({ order })
} catch (err) {
@@ -36,10 +36,7 @@ describe("POST /admin/products/:id/options", () => {
})
it("returns the updated product decorated", () => {
expect(subject.body.product._id).toEqual(
IdMap.getId("productWithOptions")
)
expect(subject.body.product.decorated).toEqual(true)
expect(subject.body.product.id).toEqual(IdMap.getId("productWithOptions"))
})
})
})
@@ -28,18 +28,18 @@ describe("POST /admin/products", () => {
})
it("returns created product draft", () => {
expect(subject.body.product._id).toEqual(IdMap.getId("product1"))
expect(subject.body.product.decorated).toEqual(true)
expect(subject.body.product.id).toEqual(IdMap.getId("product1"))
})
it("calls service createDraft", () => {
expect(ProductServiceMock.createDraft).toHaveBeenCalledTimes(1)
expect(ProductServiceMock.createDraft).toHaveBeenCalledWith({
expect(ProductServiceMock.create).toHaveBeenCalledTimes(1)
expect(ProductServiceMock.create).toHaveBeenCalledWith({
title: "Test Product",
description: "Test Description",
tags: "hi,med,dig",
handle: "test-product",
is_giftcard: false,
profile_id: IdMap.getId("default_shipping_profile"),
})
})
@@ -89,13 +89,14 @@ describe("POST /admin/products", () => {
})
it("calls service createDraft", () => {
expect(ProductServiceMock.createDraft).toHaveBeenCalledTimes(1)
expect(ProductServiceMock.createDraft).toHaveBeenCalledWith({
expect(ProductServiceMock.create).toHaveBeenCalledTimes(1)
expect(ProductServiceMock.create).toHaveBeenCalledWith({
title: "Gift Card",
description: "make someone happy",
options: [{ title: "Denominations" }],
handle: "test-gift-card",
is_giftcard: true,
profile_id: IdMap.getId("giftCardProfile"),
})
})
@@ -106,12 +107,6 @@ describe("POST /admin/products", () => {
expect(
ShippingProfileServiceMock.retrieveGiftCardDefault
).toHaveBeenCalledWith()
expect(ShippingProfileServiceMock.addProduct).toHaveBeenCalledTimes(1)
expect(ShippingProfileServiceMock.addProduct).toHaveBeenCalledWith(
IdMap.getId("giftCardProfile"),
undefined
)
})
})
@@ -1,6 +1,6 @@
import { IdMap } from "medusa-test-utils"
import { request } from "../../../../../helpers/test-request"
import { ProductServiceMock } from "../../../../../services/__mocks__/product"
import { ProductVariantServiceMock } from "../../../../../services/__mocks__/product-variant"
describe("POST /admin/products/:id/variants", () => {
describe("successful add variant", () => {
@@ -34,10 +34,11 @@ describe("POST /admin/products/:id/variants", () => {
})
it("calls service addVariant", () => {
expect(ProductServiceMock.createVariant).toHaveBeenCalledTimes(1)
expect(ProductServiceMock.createVariant).toHaveBeenCalledWith(
expect(ProductVariantServiceMock.create).toHaveBeenCalledTimes(1)
expect(ProductVariantServiceMock.create).toHaveBeenCalledWith(
IdMap.getId("productWithOptions"),
{
inventory_quantity: 0,
title: "Test Product Variant",
options: [],
prices: [
@@ -51,10 +52,7 @@ describe("POST /admin/products/:id/variants", () => {
})
it("returns the updated product decorated", () => {
expect(subject.body.product._id).toEqual(
IdMap.getId("productWithOptions")
)
expect(subject.body.product.decorated).toEqual(true)
expect(subject.body.product.id).toEqual(IdMap.getId("productWithOptions"))
})
})
})
@@ -1,6 +1,6 @@
import { IdMap } from "medusa-test-utils"
import { request } from "../../../../../helpers/test-request"
import { ProductServiceMock } from "../../../../../services/__mocks__/product"
import { ProductVariantServiceMock } from "../../../../../services/__mocks__/product-variant"
describe("POST /admin/products/:id/variants/:variantId", () => {
describe("successful removes variant", () => {
@@ -27,9 +27,8 @@ describe("POST /admin/products/:id/variants/:variantId", () => {
})
it("calls service removeVariant", () => {
expect(ProductServiceMock.deleteVariant).toHaveBeenCalledTimes(1)
expect(ProductServiceMock.deleteVariant).toHaveBeenCalledWith(
IdMap.getId("productWithOptions"),
expect(ProductVariantServiceMock.delete).toHaveBeenCalledTimes(1)
expect(ProductVariantServiceMock.delete).toHaveBeenCalledWith(
IdMap.getId("variant1")
)
})
@@ -39,19 +38,7 @@ describe("POST /admin/products/:id/variants/:variantId", () => {
variant_id: IdMap.getId("variant1"),
object: "product-variant",
deleted: true,
product: {
_id: IdMap.getId("productWithOptions"),
decorated: true,
options: [
{
_id: IdMap.getId("option1"),
title: "Test",
values: [IdMap.getId("optionValue1")],
},
],
title: "Test",
variants: [IdMap.getId("variant1")],
},
product: expect.any(Object),
})
})
})
@@ -27,13 +27,43 @@ describe("GET /admin/products/:id", () => {
it("calls get product from productSerice", () => {
expect(ProductServiceMock.retrieve).toHaveBeenCalledTimes(1)
expect(ProductServiceMock.retrieve).toHaveBeenCalledWith(
IdMap.getId("product1")
IdMap.getId("product1"),
{
select: [
"id",
"title",
"subtitle",
"description",
"tags",
"handle",
"is_giftcard",
"thumbnail",
"profile_id",
"weight",
"length",
"height",
"width",
"hs_code",
"origin_country",
"mid_code",
"material",
"created_at",
"updated_at",
"metadata",
],
relations: [
"variants",
"variants.prices",
"variants.options",
"images",
"options",
],
}
)
})
it("returns product decorated", () => {
expect(subject.body.product._id).toEqual(IdMap.getId("product1"))
expect(subject.body.product.decorated).toEqual(true)
expect(subject.body.product.id).toEqual(IdMap.getId("product1"))
})
})
})
@@ -1,39 +0,0 @@
import { IdMap } from "medusa-test-utils"
import { request } from "../../../../../helpers/test-request"
import { ProductServiceMock } from "../../../../../services/__mocks__/product"
describe("GET /admin/products/:id/variants", () => {
describe("successfully gets a product", () => {
let subject
beforeAll(async () => {
subject = await request(
"GET",
`/admin/products/${IdMap.getId("product1")}/variants`,
{
adminSession: {
jwt: {
userId: IdMap.getId("admin_user"),
},
},
}
)
})
afterAll(() => {
jest.clearAllMocks()
})
it("calls get product from productSerice", () => {
expect(ProductServiceMock.retrieveVariants).toHaveBeenCalledTimes(1)
expect(ProductServiceMock.retrieveVariants).toHaveBeenCalledWith(
IdMap.getId("product1")
)
})
it("returns variants", () => {
expect(subject.body.variants[0]._id).toEqual(IdMap.getId("1"))
expect(subject.body.variants[1]._id).toEqual(IdMap.getId("2"))
})
})
})
@@ -1,38 +0,0 @@
import { IdMap } from "medusa-test-utils"
import { request } from "../../../../../helpers/test-request"
import { ProductServiceMock } from "../../../../../services/__mocks__/product"
describe("POST /admin/products/:id/publish", () => {
describe("successful publish", () => {
let subject
beforeAll(async () => {
subject = await request(
"POST",
`/admin/products/${IdMap.getId("publish")}/publish`,
{
adminSession: {
jwt: {
userId: IdMap.getId("admin_user"),
},
},
}
)
})
it("returns 200", () => {
expect(subject.status).toEqual(200)
})
it("returns product with published flag true", () => {
expect(subject.body.product.published).toEqual(true)
})
it("calls service publish", () => {
expect(ProductServiceMock.publish).toHaveBeenCalledTimes(1)
expect(ProductServiceMock.publish).toHaveBeenCalledWith(
IdMap.getId("publish")
)
})
})
})
@@ -1,6 +1,5 @@
import { IdMap } from "medusa-test-utils"
import { request } from "../../../../../helpers/test-request"
import { ProductServiceMock } from "../../../../../services/__mocks__/product"
import { ProductVariantServiceMock } from "../../../../../services/__mocks__/product-variant"
describe("POST /admin/products/:id/variants/:variantId", () => {
@@ -41,140 +40,28 @@ describe("POST /admin/products/:id/variants/:variantId", () => {
expect(subject.status).toEqual(200)
})
it("calls service removeVariant", () => {
expect(ProductVariantServiceMock.setCurrencyPrice).toHaveBeenCalledTimes(
1
)
expect(ProductVariantServiceMock.setCurrencyPrice).toHaveBeenCalledWith(
IdMap.getId("variant1"),
"DKK",
100,
undefined
)
expect(ProductVariantServiceMock.setRegionPrice).toHaveBeenCalledTimes(1)
expect(ProductVariantServiceMock.setRegionPrice).toHaveBeenCalledWith(
IdMap.getId("variant1"),
IdMap.getId("region-fr"),
100,
undefined
)
})
it("filters prices", () => {
expect(ProductVariantServiceMock.update).toHaveBeenCalledTimes(1)
expect(ProductVariantServiceMock.update).toHaveBeenCalledWith(
IdMap.getId("variant1"),
{
title: "hi",
}
)
})
it("returns decorated product with variant removed", () => {
expect(subject.body.product._id).toEqual(
IdMap.getId("productWithOptions")
)
expect(subject.body.product.decorated).toEqual(true)
})
})
describe("successful updates options", () => {
let subject
beforeAll(async () => {
jest.clearAllMocks()
subject = await request(
"POST",
`/admin/products/${IdMap.getId(
"productWithOptions"
)}/variants/${IdMap.getId("variant1")}`,
{
payload: {
options: [
{
option_id: IdMap.getId("option_id"),
value: 100,
},
],
},
adminSession: {
jwt: {
userId: IdMap.getId("admin_user"),
prices: [
{
region_id: IdMap.getId("region-fr"),
amount: 100,
},
},
}
)
})
it("returns 200", () => {
expect(subject.status).toEqual(200)
})
it("calls service removeVariant", () => {
expect(ProductServiceMock.updateOptionValue).toHaveBeenCalledTimes(1)
expect(ProductServiceMock.updateOptionValue).toHaveBeenCalledWith(
IdMap.getId("productWithOptions"),
IdMap.getId("variant1"),
IdMap.getId("option_id"),
100
)
})
it("returns decorated product with variant removed", () => {
expect(subject.body.product._id).toEqual(
IdMap.getId("productWithOptions")
)
expect(subject.body.product.decorated).toEqual(true)
})
})
describe("successful updates variant", () => {
let subject
beforeAll(async () => {
jest.clearAllMocks()
subject = await request(
"POST",
`/admin/products/${IdMap.getId(
"productWithOptions"
)}/variants/${IdMap.getId("variant1")}`,
{
payload: {
title: "hi",
inventory_quantity: 123,
allow_backorder: true,
},
adminSession: {
jwt: {
userId: IdMap.getId("admin_user"),
{
currency_code: "DKK",
amount: 100,
},
},
}
)
})
it("returns 200", () => {
expect(subject.status).toEqual(200)
})
it("calls variant update", () => {
expect(ProductVariantServiceMock.update).toHaveBeenCalledTimes(1)
expect(ProductVariantServiceMock.update).toHaveBeenCalledWith(
IdMap.getId("variant1"),
{
title: "hi",
inventory_quantity: 123,
allow_backorder: true,
],
}
)
})
it("returns decorated product with variant removed", () => {
expect(subject.body.product._id).toEqual(
IdMap.getId("productWithOptions")
)
expect(subject.body.product.decorated).toEqual(true)
expect(subject.body.product.id).toEqual(IdMap.getId("productWithOptions"))
})
})
})
@@ -1,4 +1,5 @@
import { MedusaError, Validator } from "medusa-core-utils"
import { defaultRelations, defaultFields } from "./"
export default async (req, res) => {
const { id } = req.params
@@ -13,23 +14,14 @@ export default async (req, res) => {
try {
const productService = req.scope.resolve("productService")
const newProduct = await productService.addOption(id, value.title)
const data = await productService.decorate(
newProduct,
[
"title",
"description",
"tags",
"handle",
"images",
"thumbnail",
"options",
"published",
],
["variants"]
)
res.json({ product: data })
await productService.addOption(id, value.title)
const product = await productService.retrieve(id, {
select: defaultFields,
relations: defaultRelations,
})
res.json({ product })
} catch (err) {
throw err
}
@@ -1,40 +1,85 @@
import { MedusaError, Validator } from "medusa-core-utils"
import { defaultRelations, defaultFields } from "."
export default async (req, res) => {
const schema = Validator.object().keys({
title: Validator.string().required(),
subtitle: Validator.string().allow(""),
description: Validator.string().allow(""),
tags: Validator.string(),
tags: Validator.string().optional(),
is_giftcard: Validator.boolean().default(false),
images: Validator.array()
.items(Validator.string())
.optional(),
thumbnail: Validator.string().optional(),
handle: Validator.string().optional(),
options: Validator.array().items({
title: Validator.string().required(),
}),
images: Validator.array().items(Validator.string()),
thumbnail: Validator.string().optional(),
variants: Validator.array().items({
title: Validator.string().required(),
sku: Validator.string(),
ean: Validator.string(),
barcode: Validator.string(),
sku: Validator.string().allow(null),
ean: Validator.string().allow(null),
upc: Validator.string().allow(null),
barcode: Validator.string().allow(null),
hs_code: Validator.string().allow(null),
inventory_quantity: Validator.number().default(0),
allow_backorder: Validator.boolean().optional(),
manage_inventory: Validator.boolean().optional(),
weight: Validator.number().optional(),
length: Validator.number().optional(),
height: Validator.number().optional(),
width: Validator.number().optional(),
origin_country: Validator.string()
.optional()
.allow("")
.allow(null),
mid_code: Validator.string()
.optional()
.allow("")
.allow(null),
material: Validator.string()
.optional()
.allow("")
.allow(null),
metadata: Validator.object().optional(),
prices: Validator.array()
.items({
currency_code: Validator.string().required(),
amount: Validator.number().required(),
sale_amount: Validator.number().optional(),
})
.items(
Validator.object()
.keys({
region_id: Validator.string(),
currency_code: Validator.string().required(),
amount: Validator.number()
.integer()
.required(),
sale_amount: Validator.number().optional(),
})
.xor("region_id", "currency_code")
)
.required(),
options: Validator.array()
.items({
value: Validator.string().required(),
})
.default([]),
inventory_quantity: Validator.number().optional(),
allow_backorder: Validator.boolean().optional(),
manage_inventory: Validator.boolean().optional(),
metadata: Validator.object().optional(),
}),
metadata: Validator.object(),
handle: Validator.string(),
weight: Validator.number().optional(),
length: Validator.number().optional(),
height: Validator.number().optional(),
width: Validator.number().optional(),
hs_code: Validator.string()
.optional()
.allow(""),
origin_country: Validator.string()
.optional()
.allow(""),
mid_code: Validator.string()
.optional()
.allow(""),
material: Validator.string()
.optional()
.allow(""),
metadata: Validator.object().optional(),
})
const { value, error } = schema.validate(req.body)
@@ -43,62 +88,62 @@ export default async (req, res) => {
}
try {
const { variants } = value
delete value.variants
const productService = req.scope.resolve("productService")
const productVariantService = req.scope.resolve("productVariantService")
const shippingProfileService = req.scope.resolve("shippingProfileService")
if (!value.thumbnail && value.images && value.images.length) {
value.thumbnail = value.images[0]
}
let newProduct = await productService.createDraft(value)
const entityManager = req.scope.resolve("manager")
if (variants) {
const optionIds = value.options.map(
o => newProduct.options.find(newO => newO.title === o.title)._id
)
let newProduct
await entityManager.transaction(async manager => {
const { variants } = value
delete value.variants
await Promise.all(
variants.map(v => {
const variant = {
...v,
options: v.options.map((o, index) => ({
...o,
option_id: optionIds[index],
})),
}
return productService.createVariant(newProduct._id, variant)
})
)
}
if (!value.thumbnail && value.images && value.images.length) {
value.thumbnail = value.images[0]
}
// Add to default shipping profile
if (value.is_giftcard) {
const { _id } = await shippingProfileService.retrieveGiftCardDefault()
await shippingProfileService.addProduct(_id, newProduct._id)
} else {
const { _id } = await shippingProfileService.retrieveDefault()
await shippingProfileService.addProduct(_id, newProduct._id)
}
let shippingProfile
// Get default shipping profile
if (value.is_giftcard) {
shippingProfile = await shippingProfileService.retrieveGiftCardDefault()
} else {
shippingProfile = await shippingProfileService.retrieveDefault()
}
newProduct = await productService.decorate(
newProduct,
[
"title",
"description",
"tags",
"handle",
"images",
"thumbnail",
"options",
"published",
],
["variants"]
)
res.json({ product: newProduct })
newProduct = await productService
.withTransaction(manager)
.create({ ...value, profile_id: shippingProfile.id })
if (variants) {
const optionIds = value.options.map(
o => newProduct.options.find(newO => newO.title === o.title).id
)
await Promise.all(
variants.map(async v => {
const variant = {
...v,
options: v.options.map((o, index) => ({
...o,
option_id: optionIds[index],
})),
}
await productVariantService
.withTransaction(manager)
.create(newProduct.id, variant)
})
)
}
})
const product = await productService.retrieve(newProduct.id, {
select: defaultFields,
relations: defaultRelations,
})
res.json({ product })
} catch (err) {
console.log(err)
throw err
}
}
@@ -1,29 +1,47 @@
import { MedusaError, Validator } from "medusa-core-utils"
import { defaultFields, defaultRelations } from "./"
export default async (req, res) => {
const { id } = req.params
const schema = Validator.object().keys({
title: Validator.string().required(),
sku: Validator.string().optional(),
ean: Validator.string().optional(),
sku: Validator.string().allow(""),
ean: Validator.string().allow(""),
upc: Validator.string().allow(""),
barcode: Validator.string().allow(""),
hs_code: Validator.string().allow(""),
inventory_quantity: Validator.number().default(0),
allow_backorder: Validator.boolean().optional(),
manage_inventory: Validator.boolean().optional(),
weight: Validator.number().optional(),
length: Validator.number().optional(),
height: Validator.number().optional(),
width: Validator.number().optional(),
origin_country: Validator.string().allow(""),
mid_code: Validator.string().allow(""),
material: Validator.string().allow(""),
metadata: Validator.object().optional(),
prices: Validator.array()
.items({
currency_code: Validator.string().required(),
amount: Validator.number().required(),
sale_amount: Validator.number().optional(),
})
.items(
Validator.object()
.keys({
region_id: Validator.string(),
currency_code: Validator.string().required(),
amount: Validator.number()
.integer()
.required(),
sale_amount: Validator.number().optional(),
})
.xor("region_id", "currency_code")
)
.required(),
options: Validator.array()
.items({
option_id: Validator.objectId().required(),
option_id: Validator.string().required(),
value: Validator.string().required(),
})
.default([]),
image: Validator.string().optional(),
inventory_quantity: Validator.number().optional(),
allow_backorder: Validator.boolean().optional(),
manage_inventory: Validator.boolean().optional(),
metadata: Validator.object().optional(),
})
const { value, error } = schema.validate(req.body)
@@ -32,24 +50,17 @@ export default async (req, res) => {
}
try {
const productVariantService = req.scope.resolve("productVariantService")
const productService = req.scope.resolve("productService")
const product = await productService.createVariant(id, value)
const data = await productService.decorate(
product,
[
"title",
"description",
"is_giftcard",
"tags",
"thumbnail",
"handle",
"images",
"options",
"published",
],
["variants"]
)
res.json({ product: data })
await productVariantService.create(id, value)
const product = await productService.retrieve(id, {
select: defaultFields,
relations: defaultRelations,
})
res.json({ product })
} catch (err) {
throw err
}
@@ -1,23 +1,15 @@
import { defaultRelations, defaultFields } from "."
export default async (req, res) => {
const { id, option_id } = req.params
try {
const productService = req.scope.resolve("productService")
const product = await productService.deleteOption(id, option_id)
const data = await productService.decorate(
product,
[
"title",
"description",
"tags",
"handle",
"thumbnail",
"images",
"options",
"published",
],
["variants"]
)
await productService.deleteOption(id, option_id)
const data = await productService.retrieve(id, {
select: defaultFields,
relations: defaultRelations,
})
res.json({
option_id,
@@ -1,23 +1,17 @@
import { defaultRelations, defaultFields } from "."
export default async (req, res) => {
const { id, variant_id } = req.params
try {
const productVariantService = req.scope.resolve("productVariantService")
const productService = req.scope.resolve("productService")
const product = await productService.deleteVariant(id, variant_id)
const data = await productService.decorate(
product,
[
"title",
"description",
"tags",
"handle",
"images",
"thumbnail",
"options",
"published",
],
["variants"]
)
await productVariantService.delete(variant_id)
const data = await productService.retrieve(id, {
select: defaultFields,
relations: defaultRelations,
})
res.json({
variant_id,
@@ -1,24 +1,14 @@
import { defaultFields, defaultRelations } from "./"
export default async (req, res) => {
const { id } = req.params
const productService = req.scope.resolve("productService")
let product = await productService.retrieve(id)
product = await productService.decorate(
product,
[
"title",
"description",
"is_giftcard",
"tags",
"thumbnail",
"handle",
"images",
"options",
"published",
],
["variants"]
)
const product = await productService.retrieve(id, {
select: defaultFields,
relations: defaultRelations,
})
res.json({ product })
}
@@ -8,21 +8,12 @@ export default app => {
route.post("/", middlewares.wrap(require("./create-product").default))
route.post("/:id", middlewares.wrap(require("./update-product").default))
route.post(
"/:id/publish",
middlewares.wrap(require("./publish-product").default)
)
route.post(
"/:id/variants",
middlewares.wrap(require("./create-variant").default)
)
route.get(
"/:id/variants",
middlewares.wrap(require("./get-variants").default)
)
route.post(
"/:id/variants/:variant_id",
middlewares.wrap(require("./update-variant").default)
@@ -44,8 +35,74 @@ export default app => {
middlewares.wrap(require("./delete-option").default)
)
route.post(
"/:id/metadata",
middlewares.wrap(require("./set-metadata").default)
)
route.get("/:id", middlewares.wrap(require("./get-product").default))
route.get("/", middlewares.wrap(require("./list-products").default))
return app
}
export const defaultRelations = [
"variants",
"variants.prices",
"variants.options",
"images",
"options",
]
export const defaultFields = [
"id",
"title",
"subtitle",
"description",
"tags",
"handle",
"is_giftcard",
"thumbnail",
"profile_id",
"weight",
"length",
"height",
"width",
"hs_code",
"origin_country",
"mid_code",
"material",
"created_at",
"updated_at",
"metadata",
]
export const allowedFields = [
"id",
"title",
"subtitle",
"description",
"tags",
"handle",
"is_giftcard",
"thumbnail",
"profile_id",
"weight",
"length",
"height",
"width",
"hs_code",
"origin_country",
"mid_code",
"material",
"created_at",
"updated_at",
"metadata",
]
export const allowedRelations = [
"variants",
"variants.prices",
"images",
"options",
]
@@ -1,65 +1,33 @@
import _ from "lodash"
import { defaultFields, defaultRelations } from "./"
export default async (req, res) => {
try {
const variantService = req.scope.resolve("productVariantService")
const productService = req.scope.resolve("productService")
const queryBuilderService = req.scope.resolve("queryBuilderService")
const query = queryBuilderService.buildQuery(req.query, [
"title",
"description",
])
if ("is_giftcard" in req.query) {
query.is_giftcard = req.query.is_giftcard === "true"
}
let variantMatches = []
if ("q" in req.query) {
let textQ = req.query.q
variantMatches = await variantService.list({
$or: [
{ sku: new RegExp(textQ, "i") },
{ title: new RegExp(textQ, "i") },
],
})
query.$or = [
...query.$or,
{ variants: { $in: variantMatches.map(({ _id }) => _id.toString()) } },
]
}
const limit = parseInt(req.query.limit) || 0
const limit = parseInt(req.query.limit) || 50
const offset = parseInt(req.query.offset) || 0
let products = await productService.list(query, offset, limit)
const selector = {}
products = await Promise.all(
products.map(
async product =>
await productService.decorate(
product,
[
"title",
"description",
"is_giftcard",
"tags",
"thumbnail",
"handle",
"images",
"options",
"published",
],
["variants"]
)
)
)
if ("q" in req.query) {
selector.q = req.query.q
}
const numProducts = await productService.count()
if ("is_giftcard" in req.query) {
selector.is_giftcard = req.query.is_giftcard === "true"
}
res.json({ products, total_count: numProducts })
const listConfig = {
select: defaultFields,
relations: defaultRelations,
skip: offset,
take: limit,
}
let products = await productService.list(selector, listConfig)
res.json({ products, count: products.length, offset, limit })
} catch (error) {
throw error
}
@@ -1,27 +0,0 @@
export default async (req, res) => {
const { id } = req.params
try {
const productService = req.scope.resolve("productService")
const product = await productService.retrieve(id)
await productService.publish(product._id)
let publishedProduct = await productService.retrieve(product._id)
publishedProduct = await productService.decorate(
publishedProduct,
[
"title",
"description",
"tags",
"handle",
"thumbnail",
"images",
"options",
"published",
],
["variants"]
)
res.json({ product: publishedProduct })
} catch (error) {
throw error
}
}
@@ -1,4 +1,5 @@
import { MedusaError, Validator } from "medusa-core-utils"
import { defaultFields, defaultRelations } from "./"
export default async (req, res) => {
const { id } = req.params
@@ -14,15 +15,17 @@ export default async (req, res) => {
}
try {
const orderService = req.scope.resolve("orderService")
let order = await orderService.setMetadata(id, value.key, value.value)
order = await orderService.decorate(
order,
[],
["region", "customer", "swaps"]
)
const productService = req.scope.resolve("productService")
await productService.update(id, {
metadata: { [value.key]: value.value },
})
res.status(200).json({ order })
const product = await productService.retrieve(id, {
select: defaultFields,
relations: defaultRelations,
})
res.status(200).json({ product })
} catch (err) {
throw err
}
@@ -1,10 +1,11 @@
import { MedusaError, Validator } from "medusa-core-utils"
import { defaultFields, defaultRelations } from "./"
export default async (req, res) => {
const { id, option_id } = req.params
const schema = Validator.object().keys({
title: Validator.string(),
title: Validator.string().required(),
})
const { value, error } = schema.validate(req.body)
@@ -15,24 +16,14 @@ export default async (req, res) => {
try {
const productService = req.scope.resolve("productService")
const product = await productService.updateOption(id, option_id, value)
await productService.updateOption(id, option_id, value)
const data = await productService.decorate(
product,
[
"title",
"description",
"tags",
"handle",
"images",
"thumbnail",
"options",
"published",
],
["variants"]
)
const product = await productService.retrieve(id, {
select: defaultFields,
relations: defaultRelations,
})
res.json({ product: data })
res.json({ product })
} catch (err) {
throw err
}
@@ -1,50 +1,64 @@
import { MedusaError, Validator } from "medusa-core-utils"
import { defaultRelations, defaultFields } from "."
export default async (req, res) => {
const { id } = req.params
const schema = Validator.object().keys({
title: Validator.string(),
title: Validator.string().optional(),
description: Validator.string().optional(),
tags: Validator.string().optional(),
handle: Validator.string(),
handle: Validator.string().optional(),
weight: Validator.number().optional(),
length: Validator.number().optional(),
height: Validator.number().optional(),
width: Validator.number().optional(),
origin_country: Validator.string().allow(null, ""),
mid_code: Validator.string().allow(null, ""),
material: Validator.string().allow(null, ""),
images: Validator.array()
.items(Validator.string())
.optional()
.optional(),
thumbnail: Validator.string().optional(),
variants: Validator.array()
.items({
_id: Validator.string(),
title: Validator.string().optional(),
sku: Validator.string().optional(),
ean: Validator.string().optional(),
published: Validator.boolean(),
image: Validator.string()
.allow("")
.optional(),
barcode: Validator.string()
.allow("")
.optional(),
id: Validator.string().optional(),
title: Validator.string().allow(null),
sku: Validator.string().allow(null),
ean: Validator.string().allow(null),
barcode: Validator.string().allow(null),
prices: Validator.array().items(
Validator.object()
.keys({
region_id: Validator.string(),
currency_code: Validator.string(),
amount: Validator.number().required(),
sale_amount: Validator.number().optional(),
amount: Validator.number()
.integer()
.required(),
})
.xor("region_id", "currency_code")
),
options: Validator.array().items({
option_id: Validator.objectId().required(),
option_id: Validator.string().required(),
value: Validator.alternatives(
Validator.string(),
Validator.number()
).required(),
}),
inventory_quantity: Validator.number().optional(),
allow_backorder: Validator.boolean().optional(),
manage_inventory: Validator.boolean().optional(),
inventory_quantity: Validator.number().allow(null),
allow_backorder: Validator.boolean().allow(null),
manage_inventory: Validator.boolean().allow(null),
weight: Validator.number().optional(),
length: Validator.number().optional(),
height: Validator.number().optional(),
width: Validator.number().optional(),
hs_code: Validator.string()
.optional()
.allow(null, ""),
origin_country: Validator.string().allow(null, ""),
mid_code: Validator.string().allow(null, ""),
material: Validator.string().allow(null, ""),
metadata: Validator.object().optional(),
})
.optional(),
@@ -58,33 +72,15 @@ export default async (req, res) => {
try {
const productService = req.scope.resolve("productService")
const oldProduct = await productService.retrieve(id)
if (
!oldProduct.thumbnail &&
!value.thumbnail &&
value.images &&
value.images.length
) {
value.thumbnail = value.images[0]
}
await productService.update(id, value)
const product = await productService.update(oldProduct._id, value)
const data = await productService.decorate(
product,
[
"title",
"description",
"tags",
"handle",
"images",
"thumbnail",
"options",
"published",
],
["variants"]
)
res.json({ product: data })
const product = await productService.retrieve(id, {
select: defaultFields,
relations: defaultRelations,
})
res.json({ product })
} catch (err) {
throw err
}
@@ -1,33 +1,52 @@
import _ from "lodash"
import { MedusaError, Validator } from "medusa-core-utils"
import { defaultFields, defaultRelations } from "./"
export default async (req, res) => {
const { id, variant_id } = req.params
const schema = Validator.object().keys({
title: Validator.string().optional(),
sku: Validator.string(),
ean: Validator.string(),
sku: Validator.string().optional(),
ean: Validator.string().optional(),
barcode: Validator.string().optional(),
prices: Validator.array().items(
Validator.object()
.keys({
region_id: Validator.string(),
currency_code: Validator.string(),
amount: Validator.number().required(),
amount: Validator.number()
.integer()
.required(),
sale_amount: Validator.number().optional(),
})
.xor("region_id", "currency_code")
),
options: Validator.array().items({
option_id: Validator.objectId().required(),
option_id: Validator.string().required(),
value: Validator.alternatives(
Validator.string(),
Validator.number()
).required(),
}),
image: Validator.string().optional(),
inventory_quantity: Validator.number().optional(),
allow_backorder: Validator.boolean().optional(),
manage_inventory: Validator.boolean().optional(),
weight: Validator.number().optional(),
length: Validator.number().optional(),
height: Validator.number().optional(),
width: Validator.number().optional(),
hs_code: Validator.string()
.optional()
.allow(null, ""),
origin_country: Validator.string()
.optional()
.allow(null, ""),
mid_code: Validator.string()
.optional()
.allow(null, ""),
material: Validator.string()
.optional()
.allow(null, ""),
metadata: Validator.object().optional(),
})
@@ -40,74 +59,13 @@ export default async (req, res) => {
const productService = req.scope.resolve("productService")
const productVariantService = req.scope.resolve("productVariantService")
if (value.prices && value.prices.length) {
for (const price of value.prices) {
if (price.region_id) {
await productVariantService.setRegionPrice(
variant_id,
price.region_id,
price.amount,
price.sale_amount || undefined
)
} else {
await productVariantService.setCurrencyPrice(
variant_id,
price.currency_code,
price.amount,
price.sale_amount || undefined
)
}
}
}
await productVariantService.update(variant_id, value)
if (value.options && value.options.length) {
for (const option of value.options) {
await productService.updateOptionValue(
id,
variant_id,
option.option_id,
option.value
)
}
}
delete value.prices
delete value.options
if (!_.isEmpty(value.metadata)) {
for (let key of Object.keys(value.metadata)) {
await productVariantService.setMetadata(
variant_id,
key,
value.metadata[key]
)
}
delete value.metadata
}
if (!_.isEmpty(value)) {
await productVariantService.update(variant_id, value)
}
const product = await productService.retrieve(id)
const data = await productService.decorate(
product,
[
"title",
"description",
"tags",
"handle",
"images",
"options",
"thumbnail",
"variants",
"is_giftcard",
"published",
],
["variants"]
)
res.json({ product: data })
const product = await productService.retrieve(id, {
select: defaultFields,
relations: defaultRelations,
})
res.json({ product })
} catch (err) {
throw err
}
@@ -2,6 +2,24 @@ import { IdMap } from "medusa-test-utils"
import { request } from "../../../../../helpers/test-request"
import { RegionServiceMock } from "../../../../../services/__mocks__/region"
const defaultFields = [
"id",
"name",
"currency_code",
"tax_rate",
"tax_code",
"created_at",
"updated_at",
"deleted_at",
"metadata",
]
const defaultRelations = [
"countries",
"payment_providers",
"fulfillment_providers",
]
describe("GET /admin/regions/:region_id", () => {
describe("successful creation", () => {
let subject
@@ -24,7 +42,11 @@ describe("GET /admin/regions/:region_id", () => {
it("calls service addCountry", () => {
expect(RegionServiceMock.retrieve).toHaveBeenCalledTimes(1)
expect(RegionServiceMock.retrieve).toHaveBeenCalledWith(
IdMap.getId("testRegion")
IdMap.getId("testRegion"),
{
select: defaultFields,
relations: defaultRelations,
}
)
})
})
@@ -2,6 +2,24 @@ import { IdMap } from "medusa-test-utils"
import { request } from "../../../../../helpers/test-request"
import { RegionServiceMock } from "../../../../../services/__mocks__/region"
const defaultFields = [
"id",
"name",
"currency_code",
"tax_rate",
"tax_code",
"created_at",
"updated_at",
"deleted_at",
"metadata",
]
const defaultRelations = [
"countries",
"payment_providers",
"fulfillment_providers",
]
describe("GET /admin/regions", () => {
describe("successful creation", () => {
let subject
@@ -22,7 +40,15 @@ describe("GET /admin/regions", () => {
it("calls service addCountry", () => {
expect(RegionServiceMock.list).toHaveBeenCalledTimes(1)
expect(RegionServiceMock.list).toHaveBeenCalledWith({})
expect(RegionServiceMock.list).toHaveBeenCalledWith(
{},
{
select: defaultFields,
relations: defaultRelations,
take: 50,
skip: 0,
}
)
})
})
})
@@ -1,4 +1,5 @@
import { MedusaError, Validator } from "medusa-core-utils"
import { defaultRelations, defaultFields } from "./"
export default async (req, res) => {
const { region_id } = req.params
@@ -15,8 +16,12 @@ export default async (req, res) => {
const regionService = req.scope.resolve("regionService")
await regionService.addCountry(region_id, value.country_code)
const data = await regionService.retrieve(region_id)
res.status(200).json({ region: data })
const region = await regionService.retrieve(region_id, {
select: defaultFields,
relations: defaultRelations,
})
res.status(200).json({ region })
} catch (err) {
throw err
}
@@ -1,4 +1,5 @@
import { MedusaError, Validator } from "medusa-core-utils"
import { defaultRelations, defaultFields } from "./"
export default async (req, res) => {
const { region_id } = req.params
@@ -15,7 +16,10 @@ export default async (req, res) => {
const regionService = req.scope.resolve("regionService")
await regionService.addFulfillmentProvider(region_id, value.provider_id)
const data = await regionService.retrieve(region_id)
const data = await regionService.retrieve(region_id, {
select: defaultFields,
relations: defaultRelations,
})
res.status(200).json({ region: data })
} catch (err) {
throw err
@@ -1,4 +1,5 @@
import { MedusaError, Validator } from "medusa-core-utils"
import { defaultRelations, defaultFields } from "./"
export default async (req, res) => {
const { region_id } = req.params
@@ -15,7 +16,10 @@ export default async (req, res) => {
const regionService = req.scope.resolve("regionService")
await regionService.addPaymentProvider(region_id, value.provider_id)
const data = await regionService.retrieve(region_id)
const data = await regionService.retrieve(region_id, {
select: defaultFields,
relations: defaultRelations,
})
res.status(200).json({ region: data })
} catch (err) {
throw err
@@ -1,4 +1,5 @@
import { MedusaError, Validator } from "medusa-core-utils"
import { defaultRelations, defaultFields } from "./"
export default async (req, res) => {
const schema = Validator.object().keys({
@@ -18,8 +19,14 @@ export default async (req, res) => {
try {
const regionService = req.scope.resolve("regionService")
const data = await regionService.create(value)
res.status(200).json({ region: data })
const result = await regionService.create(value)
const region = await regionService.retrieve(result.id, {
select: defaultFields,
relations: defaultRelations,
})
res.status(200).json({ region })
} catch (err) {
throw err
}
@@ -1,9 +1,16 @@
import { defaultRelations, defaultFields } from "./"
export default async (req, res) => {
const { id, key } = req.params
try {
const regionService = req.scope.resolve("regionService")
const region = await regionService.deleteMetadata(id, key)
await regionService.deleteMetadata(id, key)
const region = await regionService.retrieve(id, {
select: defaultFields,
relations: defaultRelations,
})
res.status(200).json({ region })
} catch (err) {
@@ -1,16 +1,19 @@
import { MedusaError, Validator } from "medusa-core-utils"
export default async (req, res) => {
const { region_id } = req.params
try {
const fulfillmentProviderService = req.scope.resolve(
"fulfillmentProviderService"
)
const regionService = req.scope.resolve("regionService")
const region = await regionService.retrieve(region_id)
const region = await regionService.retrieve(region_id, {
relations: ["fulfillment_providers"],
})
const fpsIds = region.fulfillment_providers.map(fp => fp.id) || []
const options = await fulfillmentProviderService.listFulfillmentOptions(
region.fulfillment_providers || []
fpsIds
)
res.status(200).json({
@@ -1,10 +1,14 @@
import { MedusaError, Validator } from "medusa-core-utils"
import { defaultRelations, defaultFields } from "./"
export default async (req, res) => {
const { region_id } = req.params
try {
const regionService = req.scope.resolve("regionService")
const data = await regionService.retrieve(region_id)
const data = await regionService.retrieve(region_id, {
select: defaultFields,
relations: defaultRelations,
})
res.status(200).json({ region: data })
} catch (err) {
@@ -70,3 +70,21 @@ export default app => {
return app
}
export const defaultFields = [
"id",
"name",
"currency_code",
"tax_rate",
"tax_code",
"created_at",
"updated_at",
"deleted_at",
"metadata",
]
export const defaultRelations = [
"countries",
"payment_providers",
"fulfillment_providers",
]
@@ -1,11 +1,24 @@
import { MedusaError, Validator } from "medusa-core-utils"
import { defaultFields, defaultRelations } from "./"
export default async (req, res) => {
try {
const regionService = req.scope.resolve("regionService")
const data = await regionService.list({})
res.status(200).json({ regions: data })
const limit = parseInt(req.query.limit) || 50
const offset = parseInt(req.query.offset) || 0
const selector = {}
const listConfig = {
select: defaultFields,
relations: defaultRelations,
skip: offset,
take: limit,
}
let regions = await regionService.list(selector, listConfig)
res.json({ regions, count: regions.length, offset, limit })
} catch (err) {
throw err
}
@@ -1,12 +1,18 @@
import { MedusaError, Validator } from "medusa-core-utils"
import { defaultRelations, defaultFields } from "./"
export default async (req, res) => {
const { region_id, country_code } = req.params
try {
const regionService = req.scope.resolve("regionService")
const data = await regionService.removeCountry(region_id, country_code)
await regionService.removeCountry(region_id, country_code)
res.json({ region: data })
const region = await regionService.retrieve(region_id, {
select: defaultFields,
relations: defaultRelations,
})
res.json({ region })
} catch (err) {
throw err
}
@@ -1,15 +1,19 @@
import { MedusaError, Validator } from "medusa-core-utils"
import { defaultRelations, defaultFields } from "./"
export default async (req, res) => {
const { region_id, provider_id } = req.params
try {
const regionService = req.scope.resolve("regionService")
const data = await regionService.removeFulfillmentProvider(
region_id,
provider_id
)
res.json({ region: data })
await regionService.removeFulfillmentProvider(region_id, provider_id)
const region = await regionService.retrieve(region_id, {
select: defaultFields,
relations: defaultRelations,
})
res.json({ region })
} catch (err) {
throw err
}
@@ -1,15 +1,18 @@
import { MedusaError, Validator } from "medusa-core-utils"
import { defaultRelations, defaultFields } from "./"
export default async (req, res) => {
const { region_id, provider_id } = req.params
try {
const regionService = req.scope.resolve("regionService")
const data = await regionService.removePaymentProvider(
region_id,
provider_id
)
await regionService.removePaymentProvider(region_id, provider_id)
res.json({ region: data })
const region = await regionService.retrieve(region_id, {
select: defaultFields,
relations: defaultRelations,
})
res.json({ region })
} catch (err) {
throw err
}
Loaded 100 of 499 files, more files were not shown because too many files have changed in this diff. Show more