fix: merge conflicts

This commit is contained in:
zakariaelas committed 2021-09-30 20:29:58 +01:00
commit 632ad17e3f
134 files changed
+5517 -628

No files matched your search

+33 -7
View File
@@ -2246,6 +2246,11 @@ doctrine@^3.0.0:
dependencies:
esutils "^2.0.2"
dom-walk@^0.1.0:
version "0.1.2"
resolved "https://registry.yarnpkg.com/dom-walk/-/dom-walk-0.1.2.tgz#0c548bef048f4d1f2a97249002236060daa3fd84"
integrity sha512-6QvTW9mrGeIegrFXdtQi9pk7O/nSK6lSdXW2eqUspN5LWD7UTji2Fqw5V2YLjBpHEoU9Xl/eUWNpDeZvoyOv2w==
domexception@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/domexception/-/domexception-1.0.1.tgz#937442644ca6a31261ef36e3ec677fe805582c90"
@@ -2870,6 +2875,14 @@ glob@^7.0.0, glob@^7.1.1, glob@^7.1.2, glob@^7.1.3, glob@^7.1.4:
once "^1.3.0"
path-is-absolute "^1.0.0"
global@^4.4.0:
version "4.4.0"
resolved "https://registry.yarnpkg.com/global/-/global-4.4.0.tgz#3e7b105179006a323ed71aafca3e9c57a5cc6406"
integrity sha512-wv/LAoHdRE3BeTGz53FAamhGlPLhlssK45usmGFThIi4XqnBmjKQ16u+RNbP7WvigRZDxUsM0J3gcQ5yicaL0w==
dependencies:
min-document "^2.19.0"
process "^0.11.10"
globals@^11.1.0:
version "11.12.0"
resolved "https://registry.yarnpkg.com/globals/-/globals-11.12.0.tgz#ab8795338868a0babd8525758018c2a7eb95c42e"
@@ -4091,16 +4104,17 @@ medusa-core-utils@^0.1.27:
"@hapi/joi" "^16.1.8"
joi-objectid "^3.0.1"
medusa-telemetry@^0.0.3:
version "0.0.3"
resolved "https://registry.yarnpkg.com/medusa-telemetry/-/medusa-telemetry-0.0.3.tgz#c11e5e0f3cc969f3eaee41d1c24f78a5c0715362"
integrity sha512-Qb/sgOwO8t2Sjjo4nKyBa6hKZ/SjniT4eEWenygEaJDqXZhfogVYGhWc5gn4tLlFFNEHXzDTlrqX2LvzfEJWIw==
medusa-telemetry@^0.0.5:
version "0.0.5"
resolved "https://registry.yarnpkg.com/medusa-telemetry/-/medusa-telemetry-0.0.5.tgz#d7d08fca5cbecc0e853b4e0406194a92c5206caa"
integrity sha512-h7hP5Lc33OkFhMcvfrPcwINzMOuPoG8Vn8O6niKGFxF9RmmQnJgaAG1J43/Eq9ZWBrWi0n42XBttibKwCMViHw==
dependencies:
axios "^0.21.1"
axios-retry "^3.1.9"
boxen "^5.0.1"
ci-info "^3.2.0"
configstore "5.0.1"
global "^4.4.0"
is-docker "^2.2.1"
remove-trailing-slash "^0.1.1"
uuid "^8.3.2"
@@ -4167,6 +4181,13 @@ mimic-fn@^2.1.0:
resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-2.1.0.tgz#7ed2c2ccccaf84d3ffcb7a69b57711fc2083401b"
integrity sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==
min-document@^2.19.0:
version "2.19.0"
resolved "https://registry.yarnpkg.com/min-document/-/min-document-2.19.0.tgz#7bd282e3f5842ed295bb748cdd9f1ffa2c824685"
integrity sha1-e9KC4/WELtKVu3SM3Z8f+iyCRoU=
dependencies:
dom-walk "^0.1.0"
minimatch@^3.0.4:
version "3.0.4"
resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083"
@@ -4706,6 +4727,11 @@ process-nextick-args@~2.0.0:
resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.1.tgz#7820d9b16120cc55ca9ae7792680ae7dba6d7fe2"
integrity sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==
process@^0.11.10:
version "0.11.10"
resolved "https://registry.yarnpkg.com/process/-/process-0.11.10.tgz#7332300e840161bda3e69a1d1d91a7d4bc16f182"
integrity sha1-czIwDoQBYb2j5podHZGn1LwW8YI=
progress@^2.0.0:
version "2.0.3"
resolved "https://registry.yarnpkg.com/progress/-/progress-2.0.3.tgz#7e8cf8d8f5b8f239c1bc68beb4eb78567d572ef8"
@@ -5561,9 +5587,9 @@ tmp@^0.0.33:
os-tmpdir "~1.0.2"
tmpl@1.0.x:
version "1.0.4"
resolved "https://registry.yarnpkg.com/tmpl/-/tmpl-1.0.4.tgz#23640dd7b42d00433911140820e5cf440e521dd1"
integrity sha1-I2QN17QtAEM5ERQIIOXPRA5SHdE=
version "1.0.5"
resolved "https://registry.yarnpkg.com/tmpl/-/tmpl-1.0.5.tgz#8683e0b902bb9c20c4f726e3c0b69f36518c07cc"
integrity sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==
to-fast-properties@^2.0.0:
version "2.0.0"
+3 -3
View File
@@ -4938,9 +4938,9 @@ tmp@^0.0.33:
os-tmpdir "~1.0.2"
tmpl@1.0.x:
version "1.0.4"
resolved "https://registry.yarnpkg.com/tmpl/-/tmpl-1.0.4.tgz#23640dd7b42d00433911140820e5cf440e521dd1"
integrity sha1-I2QN17QtAEM5ERQIIOXPRA5SHdE=
version "1.0.5"
resolved "https://registry.yarnpkg.com/tmpl/-/tmpl-1.0.5.tgz#8683e0b902bb9c20c4f726e3c0b69f36518c07cc"
integrity sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==
to-fast-properties@^2.0.0:
version "2.0.0"
+13
View File
@@ -0,0 +1,13 @@
{
"plugins": [
"@babel/plugin-proposal-class-properties",
"@babel/plugin-transform-instanceof",
"@babel/plugin-transform-classes"
],
"presets": ["@babel/preset-env"],
"env": {
"test": {
"plugins": ["@babel/plugin-transform-runtime"]
}
}
}
+9
View File
@@ -0,0 +1,9 @@
{
"plugins": ["prettier"],
"extends": ["prettier"],
"rules": {
"prettier/prettier": "error",
"semi": "error",
"no-unused-expressions": "true"
}
}
+16
View File
@@ -0,0 +1,16 @@
/lib
node_modules
.DS_store
.env*
/*.js
!index.js
yarn.lock
/dist
/api
/services
/models
/subscribers
/__mocks__
+9
View File
@@ -0,0 +1,9 @@
/lib
node_modules
.DS_store
.env*
/*.js
!index.js
yarn.lock
+7
View File
@@ -0,0 +1,7 @@
{
"endOfLine": "lf",
"semi": false,
"singleQuote": false,
"tabWidth": 2,
"trailingComma": "es5"
}
+1
View File
@@ -0,0 +1 @@
// noop
+46
View File
@@ -0,0 +1,46 @@
{
"name": "medusa-file-s3",
"version": "1.0.0",
"description": "AWS s3 file connector for Medusa",
"main": "index.js",
"repository": {
"type": "git",
"url": "https://github.com/medusajs/medusa",
"directory": "packages/medusa-file-s3"
},
"author": "Sebastian Mateos Nicolajsen",
"license": "MIT",
"devDependencies": {
"@babel/cli": "^7.7.5",
"@babel/core": "^7.7.5",
"@babel/node": "^7.7.4",
"@babel/plugin-proposal-class-properties": "^7.7.4",
"@babel/plugin-transform-instanceof": "^7.8.3",
"@babel/plugin-transform-runtime": "^7.7.6",
"@babel/preset-env": "^7.7.5",
"@babel/register": "^7.7.4",
"@babel/runtime": "^7.9.6",
"client-sessions": "^0.8.0",
"cross-env": "^5.2.1",
"eslint": "^6.8.0",
"jest": "^25.5.2",
"medusa-test-utils": "^0.3.0"
},
"scripts": {
"build": "babel src -d .",
"prepare": "cross-env NODE_ENV=production npm run build",
"watch": "babel -w src --out-dir . --ignore **/__tests__",
"test": "jest"
},
"peerDependencies": {
"medusa-interfaces": "1.x"
},
"dependencies": {
"@babel/plugin-transform-classes": "^7.15.4",
"aws-sdk": "^2.983.0",
"body-parser": "^1.19.0",
"express": "^4.17.1",
"medusa-core-utils": "^1.1.20",
"medusa-test-utils": "^1.1.23"
}
}
@@ -0,0 +1,73 @@
import fs from "fs"
import aws from "aws-sdk"
import { FileService } from "medusa-interfaces"
class S3Service extends FileService {
constructor({}, options) {
super()
this.bucket_ = options.bucket
this.s3Url_ = options.s3_url
this.accessKeyId_ = options.access_key_id
this.secretAccessKey_ = options.secret_access_key
this.region_ = options.region
this.endpoint_ = options.endpoint
}
upload(file) {
aws.config.setPromisesDependency()
aws.config.update({
accessKeyId: this.accessKeyId_,
secretAccessKey: this.secretAccessKey_,
region: this.region_,
endpoint: this.endpoint_,
})
const s3 = new aws.S3()
var params = {
ACL: "public-read",
Bucket: this.bucket_,
Body: fs.createReadStream(file.path),
Key: `${file.originalname}`,
}
return new Promise((resolve, reject) => {
s3.upload(params, (err, data) => {
if (err) {
reject(err)
return
}
resolve({ url: data.Location })
})
})
}
delete(file) {
aws.config.setPromisesDependency()
aws.config.update({
accessKeyId: this.accessKeyId_,
secretAccessKey: this.secretAccessKey_,
region: this.region_,
endpoint: this.endpoint_,
})
const s3 = new aws.S3()
var params = {
Bucket: this.bucket_,
Key: `${file}`,
}
return new Promise((resolve, reject) => {
s3.deleteObject(params, (err, data) => {
if (err) {
reject(err)
return
}
resolve(data)
})
})
}
}
export default S3Service
@@ -3,6 +3,14 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.1.26](https://github.com/medusajs/medusa/compare/medusa-payment-stripe@1.1.25...medusa-payment-stripe@1.1.26) (2021-09-22)
**Note:** Version bump only for package medusa-payment-stripe
## [1.1.25](https://github.com/medusajs/medusa/compare/medusa-payment-stripe@1.1.24...medusa-payment-stripe@1.1.25) (2021-09-15)
**Note:** Version bump only for package medusa-payment-stripe
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "medusa-payment-stripe",
"version": "1.1.25",
"version": "1.1.26",
"description": "Stripe Payment provider for Meduas Commerce",
"main": "index.js",
"repository": {
@@ -333,7 +333,7 @@ class StripeProviderService extends PaymentService {
async cancelPayment(payment) {
const { id } = payment.data
try {
return this.stripe_.paymentIntents.cancel(id)
return await this.stripe_.paymentIntents.cancel(id)
} catch (error) {
if (error.payment_intent.status === "canceled") {
return error.payment_intent
+56 -3
View File
@@ -857,6 +857,18 @@
exec-sh "^0.3.2"
minimist "^1.2.0"
"@hapi/hoek@^9.0.0":
version "9.2.0"
resolved "https://registry.yarnpkg.com/@hapi/hoek/-/hoek-9.2.0.tgz#f3933a44e365864f4dad5db94158106d511e8131"
integrity sha512-sqKVVVOe5ivCaXDWivIJYVSaEgdQK9ul7a4Kity5Iw7u9+wBAPbX1RMSnLLmp7O4Vzj0WOWwMAJsTL00xwaNug==
"@hapi/topo@^5.0.0":
version "5.1.0"
resolved "https://registry.yarnpkg.com/@hapi/topo/-/topo-5.1.0.tgz#dc448e332c6c6e37a4dc02fd84ba8d44b9afb012"
integrity sha512-foQZKJig7Ob0BMAYBfcJk8d77QtOe7Wo4ox7ff1lQYoNNAb6jwcY1ncdoy2e9wQZzvNy7ODZCYJkK8kzmcAnAg==
dependencies:
"@hapi/hoek" "^9.0.0"
"@istanbuljs/load-nyc-config@^1.0.0":
version "1.1.0"
resolved "https://registry.yarnpkg.com/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz#fd3db1d59ecf7cf121e80650bb86712f9b55eced"
@@ -1058,6 +1070,23 @@
readdirp "^2.2.1"
upath "^1.1.1"
"@sideway/address@^4.1.0":
version "4.1.2"
resolved "https://registry.yarnpkg.com/@sideway/address/-/address-4.1.2.tgz#811b84333a335739d3969cfc434736268170cad1"
integrity sha512-idTz8ibqWFrPU8kMirL0CoPH/A29XOzzAzpyN3zQ4kAWnzmNfFmRaoMNN6VI8ske5M73HZyhIaW4OuSFIdM4oA==
dependencies:
"@hapi/hoek" "^9.0.0"
"@sideway/formula@^3.0.0":
version "3.0.0"
resolved "https://registry.yarnpkg.com/@sideway/formula/-/formula-3.0.0.tgz#fe158aee32e6bd5de85044be615bc08478a0a13c"
integrity sha512-vHe7wZ4NOXVfkoRb8T5otiENVlT7a3IAiw7H5M2+GO+9CDgcVUUsX1zalAztCmwyOr2RUTGJdgB+ZvSVqmdHmg==
"@sideway/pinpoint@^2.0.0":
version "2.0.0"
resolved "https://registry.yarnpkg.com/@sideway/pinpoint/-/pinpoint-2.0.0.tgz#cff8ffadc372ad29fd3f78277aeb29e632cc70df"
integrity sha512-RNiOoTPkptFtSVzQevY/yWtZwf/RxyVnPy/OcA9HBM3MlGDnBEYL5B41H0MTn0Uec8Hi+2qUtTfG2WWZBmMejQ==
"@sinonjs/commons@^1.7.0":
version "1.8.2"
resolved "https://registry.yarnpkg.com/@sinonjs/commons/-/commons-1.8.2.tgz#858f5c4b48d80778fde4b9d541f27edc0d56488b"
@@ -3167,6 +3196,22 @@ jest@^25.5.2:
import-local "^3.0.2"
jest-cli "^25.5.4"
joi-objectid@^3.0.1:
version "3.0.1"
resolved "https://registry.yarnpkg.com/joi-objectid/-/joi-objectid-3.0.1.tgz#63ace7860f8e1a993a28d40c40ffd8eff01a3668"
integrity sha512-V/3hbTlGpvJ03Me6DJbdBI08hBTasFOmipsauOsxOSnsF1blxV537WTl1zPwbfcKle4AK0Ma4OPnzMH4LlvTpQ==
joi@^17.3.0:
version "17.4.2"
resolved "https://registry.yarnpkg.com/joi/-/joi-17.4.2.tgz#02f4eb5cf88e515e614830239379dcbbe28ce7f7"
integrity sha512-Lm56PP+n0+Z2A2rfRvsfWVDXGEWjXxatPopkQ8qQ5mxCEhwHG+Ettgg5o98FFaxilOxozoa14cFhrE/hOzh/Nw==
dependencies:
"@hapi/hoek" "^9.0.0"
"@hapi/topo" "^5.0.0"
"@sideway/address" "^4.1.0"
"@sideway/formula" "^3.0.0"
"@sideway/pinpoint" "^2.0.0"
js-tokens@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499"
@@ -3379,6 +3424,14 @@ math-random@^1.0.1:
resolved "https://registry.yarnpkg.com/math-random/-/math-random-1.0.4.tgz#5dd6943c938548267016d4e34f057583080c514c"
integrity sha512-rUxjysqif/BZQH2yhd5Aaq7vXMSx9NdEsQcyA07uEzIvxgI7zIr33gGsh+RU0/XjmQpCW7RsVof1vlkvQVCK5A==
medusa-core-utils@^1.1.22:
version "1.1.22"
resolved "https://registry.yarnpkg.com/medusa-core-utils/-/medusa-core-utils-1.1.22.tgz#84ce0af0a7c672191d758ea462056e30a39d08b1"
integrity sha512-kMuRkWOuNG4Bw6epg/AYu95UJuE+rjHTeTWRLbEPrYGjWREV82tLWVDI21/QcccmaHmMU98Rkw2z9JwyFZIiyw==
dependencies:
joi "^17.3.0"
joi-objectid "^3.0.1"
merge-stream@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/merge-stream/-/merge-stream-2.0.0.tgz#52823629a14dd00c9770fb6ad47dc6310f2c1f60"
@@ -4516,9 +4569,9 @@ tmp@^0.0.33:
os-tmpdir "~1.0.2"
tmpl@1.0.x:
version "1.0.4"
resolved "https://registry.yarnpkg.com/tmpl/-/tmpl-1.0.4.tgz#23640dd7b42d00433911140820e5cf440e521dd1"
integrity sha1-I2QN17QtAEM5ERQIIOXPRA5SHdE=
version "1.0.5"
resolved "https://registry.yarnpkg.com/tmpl/-/tmpl-1.0.5.tgz#8683e0b902bb9c20c4f726e3c0b69f36518c07cc"
integrity sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==
to-fast-properties@^2.0.0:
version "2.0.0"
+1
View File
@@ -63,6 +63,7 @@
"glob": "^7.1.6",
"ioredis": "^4.17.3",
"ioredis-mock": "^5.6.0",
"iso8601-duration": "^1.3.0",
"joi": "^17.3.0",
"joi-objectid": "^3.0.1",
"jsonwebtoken": "^8.5.1",
@@ -12,6 +12,9 @@ import { Validator, MedusaError } from "medusa-core-utils"
* application/json:
* schema:
* properties:
* email:
* type: string
* description: The Customer's email. Only providable if user not registered.
* first_name:
* type: string
* description: The Customer's first name.
@@ -37,6 +40,7 @@ export default async (req, res) => {
const { id } = req.params
const schema = Validator.object().keys({
email: Validator.string().optional(),
first_name: Validator.string().optional(),
last_name: Validator.string().optional(),
password: Validator.string().optional(),
@@ -50,9 +54,19 @@ export default async (req, res) => {
try {
const customerService = req.scope.resolve("customerService")
let customer = await customerService.retrieve(id)
if (value.email && customer.has_account) {
throw new MedusaError(
MedusaError.Types.INVALID_DATA,
"Email cannot be changed when the user has registered their account"
)
}
await customerService.update(id, value)
const customer = await customerService.retrieve(id, {
customer = await customerService.retrieve(id, {
relations: ["orders"],
})
res.status(200).json({ customer })
@@ -46,6 +46,7 @@ describe("POST /admin/discounts/:discount_id/regions/:region_id", () => {
"updated_at",
"deleted_at",
"metadata",
"valid_duration",
],
relations: ["rule", "parent_discount", "regions", "rule.valid_for"],
}
@@ -46,6 +46,7 @@ describe("POST /admin/discounts/:discount_id/variants/:variant_id", () => {
"updated_at",
"deleted_at",
"metadata",
"valid_duration",
],
relations: ["rule", "parent_discount", "regions", "rule.valid_for"],
}
@@ -16,6 +16,8 @@ describe("POST /admin/discounts", () => {
value: 10,
allocation: "total",
},
starts_at: "02/02/2021 13:45",
ends_at: "03/14/2021 04:30",
},
adminSession: {
jwt: {
@@ -39,12 +41,99 @@ describe("POST /admin/discounts", () => {
value: 10,
allocation: "total",
},
starts_at: new Date("02/02/2021 13:45"),
ends_at: new Date("03/14/2021 04:30"),
is_disabled: false,
is_dynamic: false,
})
})
})
describe("unsuccessful creation with dynamic discount using an invalid iso8601 duration", () => {
let subject
beforeAll(async () => {
jest.clearAllMocks()
subject = await request("POST", "/admin/discounts", {
payload: {
code: "TEST",
rule: {
description: "Test",
type: "fixed",
value: 10,
allocation: "total",
},
starts_at: "02/02/2021 13:45",
is_dynamic: true,
valid_duration: "PaMT2D",
},
adminSession: {
jwt: {
userId: IdMap.getId("admin_user"),
},
},
})
})
it("returns 400", () => {
expect(subject.status).toEqual(400)
})
it("returns error", () => {
expect(subject.body.message[0].message).toEqual(
`"valid_duration" must be a valid ISO 8601 duration`
)
})
})
describe("successful creation with dynamic discount", () => {
let subject
beforeAll(async () => {
jest.clearAllMocks()
subject = await request("POST", "/admin/discounts", {
payload: {
code: "TEST",
rule: {
description: "Test",
type: "fixed",
value: 10,
allocation: "total",
},
starts_at: "02/02/2021 13:45",
is_dynamic: true,
valid_duration: "P1Y2M03DT04H05M",
},
adminSession: {
jwt: {
userId: IdMap.getId("admin_user"),
},
},
})
})
it("returns 200", () => {
expect(subject.status).toEqual(200)
})
it("calls service create", () => {
expect(DiscountServiceMock.create).toHaveBeenCalledTimes(1)
expect(DiscountServiceMock.create).toHaveBeenCalledWith({
code: "TEST",
rule: {
description: "Test",
type: "fixed",
value: 10,
allocation: "total",
},
starts_at: new Date("02/02/2021 13:45"),
is_disabled: false,
is_dynamic: true,
valid_duration: "P1Y2M03DT04H05M",
})
})
})
describe("fails on invalid data", () => {
let subject
@@ -74,4 +163,84 @@ describe("POST /admin/discounts", () => {
expect(subject.body.message[0].message).toEqual(`"rule.type" is required`)
})
})
describe("fails on invalid date intervals", () => {
let subject
beforeAll(async () => {
subject = await request("POST", "/admin/discounts", {
payload: {
code: "TEST",
rule: {
description: "Test",
type: "fixed",
value: 10,
allocation: "total",
},
ends_at: "02/02/2021",
starts_at: "03/14/2021",
},
adminSession: {
jwt: {
userId: IdMap.getId("admin_user"),
},
},
})
})
it("returns 400", () => {
expect(subject.status).toEqual(400)
})
it("returns error", () => {
expect(subject.body.message[0].message).toEqual(
`"ends_at" must be greater than "ref:starts_at"`
)
})
})
describe("succesfully creates a dynamic discount without setting valid duration", () => {
let subject
beforeAll(async () => {
jest.clearAllMocks()
subject = await request("POST", "/admin/discounts", {
payload: {
code: "TEST",
is_dynamic: true,
rule: {
description: "Test",
type: "fixed",
value: 10,
allocation: "total",
},
starts_at: "03/14/2021 14:30",
},
adminSession: {
jwt: {
userId: IdMap.getId("admin_user"),
},
},
})
})
it("returns 200", () => {
expect(subject.status).toEqual(200)
})
it("returns error", () => {
expect(DiscountServiceMock.create).toHaveBeenCalledWith({
code: "TEST",
is_dynamic: true,
is_disabled: false,
rule: {
description: "Test",
type: "fixed",
value: 10,
allocation: "total",
},
starts_at: new Date("03/14/2021 14:30"),
})
})
})
})
@@ -17,6 +17,7 @@ const defaultFields = [
"updated_at",
"deleted_at",
"metadata",
"valid_duration",
]
const defaultRelations = [
@@ -17,6 +17,7 @@ const defaultFields = [
"updated_at",
"deleted_at",
"metadata",
"valid_duration",
]
const defaultRelations = [
@@ -17,6 +17,7 @@ const defaultFields = [
"updated_at",
"deleted_at",
"metadata",
"valid_duration",
]
const defaultRelations = [
@@ -7,6 +7,7 @@ describe("POST /admin/discounts", () => {
let subject
beforeAll(async () => {
jest.clearAllMocks()
subject = await request(
"POST",
`/admin/discounts/${IdMap.getId("total10")}`,
@@ -50,4 +51,139 @@ describe("POST /admin/discounts", () => {
)
})
})
describe("unsuccessful update with dynamic discount using an invalid iso8601 duration", () => {
let subject
beforeAll(async () => {
jest.clearAllMocks()
subject = await request(
"POST",
`/admin/discounts/${IdMap.getId("total10")}`,
{
payload: {
code: "10TOTALOFF",
rule: {
id: "1234",
type: "fixed",
value: 10,
allocation: "total",
},
starts_at: "02/02/2021 13:45",
is_dynamic: true,
valid_duration: "PaMT2D",
},
adminSession: {
jwt: {
userId: IdMap.getId("admin_user"),
},
},
}
)
})
it("returns 400", () => {
expect(subject.status).toEqual(400)
})
it("returns error", () => {
expect(subject.body.message[0].message).toEqual(
`"valid_duration" must be a valid ISO 8601 duration`
)
})
})
describe("successful update with dynamic discount", () => {
let subject
beforeAll(async () => {
jest.clearAllMocks()
subject = await request(
"POST",
`/admin/discounts/${IdMap.getId("total10")}`,
{
payload: {
code: "10TOTALOFF",
rule: {
id: "1234",
type: "fixed",
value: 10,
allocation: "total",
},
starts_at: "02/02/2021 13:45",
is_dynamic: true,
valid_duration: "P1Y2M03DT04H05M",
},
adminSession: {
jwt: {
userId: IdMap.getId("admin_user"),
},
},
}
)
})
it("returns 200", () => {
expect(subject.status).toEqual(200)
})
it("calls service update", () => {
expect(DiscountServiceMock.update).toHaveBeenCalledTimes(1)
expect(DiscountServiceMock.update).toHaveBeenCalledWith(
IdMap.getId("total10"),
{
code: "10TOTALOFF",
rule: {
id: "1234",
type: "fixed",
value: 10,
allocation: "total",
},
starts_at: new Date("02/02/2021 13:45"),
is_dynamic: true,
valid_duration: "P1Y2M03DT04H05M",
}
)
})
})
describe("fails on invalid date intervals", () => {
let subject
beforeAll(async () => {
jest.clearAllMocks()
subject = await request(
"POST",
`/admin/discounts/${IdMap.getId("total10")}`,
{
payload: {
code: "10TOTALOFF",
rule: {
id: "1234",
type: "fixed",
value: 10,
allocation: "total",
},
ends_at: "02/02/2021",
starts_at: "03/14/2021",
},
adminSession: {
jwt: {
userId: IdMap.getId("admin_user"),
},
},
}
)
})
it("returns 400", () => {
expect(subject.status).toEqual(400)
})
it("returns error", () => {
expect(subject.body.message[0].message).toEqual(
`"ends_at" must be greater than "ref:starts_at"`
)
})
})
})
@@ -71,7 +71,13 @@ export default async (req, res) => {
.required(),
is_disabled: Validator.boolean().default(false),
starts_at: Validator.date().optional(),
ends_at: Validator.date().optional(),
ends_at: Validator.date()
.greater(Validator.ref("starts_at"))
.optional(),
valid_duration: Validator.string()
.isoDuration()
.allow(null)
.optional(),
usage_limit: Validator.number()
.positive()
.optional(),
@@ -37,9 +37,9 @@ export default async (req, res) => {
try {
const discountService = req.scope.resolve("discountService")
await discountService.createDynamicCode(discount_id, value)
const created = await discountService.createDynamicCode(discount_id, value)
const discount = await discountService.retrieve(discount_id, {
const discount = await discountService.retrieve(created.id, {
relations: ["rule", "rule.valid_for", "regions"],
})
@@ -74,6 +74,7 @@ export const defaultFields = [
"updated_at",
"deleted_at",
"metadata",
"valid_duration",
]
export const defaultRelations = [
@@ -68,7 +68,16 @@ export default async (req, res) => {
.optional(),
is_disabled: Validator.boolean().optional(),
starts_at: Validator.date().optional(),
ends_at: Validator.date().optional(),
ends_at: Validator.when("starts_at", {
not: undefined,
then: Validator.date()
.greater(Validator.ref("starts_at"))
.optional(),
otherwise: Validator.date().optional(),
}),
valid_duration: Validator.string()
.isoDuration().allow(null)
.optional(),
usage_limit: Validator.number()
.positive()
.optional(),
@@ -78,6 +87,7 @@ export default async (req, res) => {
})
const { value, error } = schema.validate(req.body)
if (error) {
throw new MedusaError(MedusaError.Types.INVALID_DATA, error.details)
}
@@ -22,6 +22,7 @@ import variantRoutes from "./variants"
import draftOrderRoutes from "./draft-orders"
import collectionRoutes from "./collections"
import notificationRoutes from "./notifications"
import noteRoutes from "./notes"
const route = Router()
@@ -68,6 +69,7 @@ export default (app, container, config) => {
collectionRoutes(route)
notificationRoutes(route)
returnReasonRoutes(route)
noteRoutes(route)
return app
}
@@ -0,0 +1,63 @@
import { MedusaError, Validator } from "medusa-core-utils"
/**
* @oas [post] /notes
* operationId: "PostNotes"
* summary: "Creates a Note"
* description: "Creates a Note which can be associated with any resource as required."
* requestBody:
* content:
* application/json:
* schema:
* properties:
* resource_id:
* type: string
* description: The id of the resource which the Note relates to.
* resource_type:
* type: string
* description: The type of resource which the Note relates to.
* value:
* type: string
* description: The content of the Note to create.
* tags:
* - Note
* responses:
* 200:
* description: OK
* content:
* application/json:
* schema:
* properties:
* note:
* $ref: "#/components/schemas/note"
*
*/
export default async (req, res) => {
const schema = Validator.object().keys({
resource_id: Validator.string(),
resource_type: Validator.string(),
value: Validator.string(),
})
const userId = req.user.id || req.user.userId
const { value, error } = schema.validate(req.body)
if (error) {
throw new MedusaError(MedusaError.Types.INVALID_DATA, error.details)
}
try {
const noteService = req.scope.resolve("noteService")
const result = await noteService.create({
resource_id: value.resource_id,
resource_type: value.resource_type,
value: value.value,
author_id: userId,
})
res.status(200).json({ note: result })
} catch (err) {
throw err
}
}
@@ -0,0 +1,35 @@
/**
* @oas [delete] /notes/{id}
* operationId: "DeleteNotesNote"
* summary: "Deletes a Note"
* description: "Deletes a Note."
* parameters:
* - (path) id=* {string} The id of the Note to delete.
* tags:
* - Note
* responses:
* 200:
* description: OK
* content:
* application/json:
* schema:
* properties:
* id:
* type: string
* description: The id of the deleted Note.
* deleted:
* type: boolean
* description: Whether or not the Note was deleted.
*/
export default async (req, res) => {
const { id } = req.params
try {
const noteService = req.scope.resolve("noteService")
await noteService.delete(id)
res.status(200).json({ id, deleted: true })
} catch (err) {
throw err
}
}
@@ -0,0 +1,31 @@
/**
* @oas [get] /notes/{id}
* operationId: "GetNoteNote"
* summary: "Get Note"
* description: "Retrieves a single note using its id"
* parameters:
* - (path) id=* {string} The id of the note to retrieve.
* tags:
* - Note
* responses:
* 200:
* description: OK
* content:
* application/json:
* schema:
* properties:
* note:
* $ref: "#/components/schemas/note"
*/
export default async (req, res) => {
const { id } = req.params
try {
const noteService = req.scope.resolve("noteService")
const note = await noteService.retrieve(id, { relations: ["author"] })
res.status(200).json({ note })
} catch (err) {
throw err
}
}
@@ -0,0 +1,20 @@
import { Router } from "express"
import middlewares from "../../../middlewares"
const route = Router()
export default app => {
app.use("/notes", route)
route.get("/:id", middlewares.wrap(require("./get-note").default))
route.get("/", middlewares.wrap(require("./list-notes").default))
route.post("/", middlewares.wrap(require("./create-note").default))
route.post("/:id", middlewares.wrap(require("./update-note").default))
route.delete("/:id", middlewares.wrap(require("./delete-note").default))
return app
}
@@ -0,0 +1,42 @@
/**
* @oas [get] /notes
* operationId: "GetNotes"
* summary: "List Notes"
* description: "Retrieves a list of notes"
* tags:
* - Note
* responses:
* 200:
* description: OK
* content:
* application/json:
* schema:
* properties:
* notes:
* type: array
* items:
* $ref: "#/components/schemas/note"
*/
export default async (req, res) => {
try {
const limit = parseInt(req.query.limit) || 50
const offset = parseInt(req.query.offset) || 0
const selector = {}
if ("resource_id" in req.query) {
selector.resource_id = req.query.resource_id
}
const noteService = req.scope.resolve("noteService")
const notes = await noteService.list(selector, {
take: limit,
skip: offset,
relations: ["author"],
})
res.status(200).json({ notes })
} catch (err) {
throw err
}
}
@@ -0,0 +1,51 @@
import { MedusaError, Validator } from "medusa-core-utils"
/**
* @oas [post] /notes/{id}
* operationId: "PostNotesNote"
* summary: "Updates a Note"
* description: "Updates a Note associated with some resource"
* parameters:
* - (path) id=* {string} The id of the Note to update
* requestBody:
* content:
* application/json:
* schema:
* properties:
* value:
* type: string
* description: The updated description of the Note.
* tags:
* - Note
* responses:
* 200:
* description: OK
* content:
* application/json:
* schema:
* properties:
* note:
* $ref: "#/components/schemas/note"
*
*/
export default async (req, res) => {
const { id } = req.params
const schema = Validator.object().keys({
value: Validator.string(),
})
const { value, error } = schema.validate(req.body)
if (error) {
throw new MedusaError(MedusaError.Types.INVALID_DATA, error.details)
}
try {
const noteService = req.scope.resolve("noteService")
const result = await noteService.update(id, value.value)
res.status(200).json({ note: result })
} catch (err) {
throw err
}
}
@@ -48,6 +48,9 @@ import { defaultFields, defaultRelations } from "./"
* no_notification:
* description: If set to true no notification will be send related to this Swap.
* type: boolean
* allow_backorder:
* description: If true, swaps can be completed with items out of stock
* type: boolean
* tags:
* - Order
* responses:
@@ -89,6 +92,7 @@ export default async (req, res) => {
quantity: Validator.number().required(),
}),
no_notification: Validator.boolean().optional(),
allow_backorder: Validator.boolean().default(true),
})
const { value, error } = schema.validate(req.body)
@@ -148,6 +152,7 @@ export default async (req, res) => {
{
idempotency_key: idempotencyKey.idempotency_key,
no_notification: value.no_notification,
allow_backorder: value.allow_backorder,
}
)
@@ -110,6 +110,7 @@ describe("POST /admin/products", () => {
description: "Test Description",
tags: [{ id: "test", value: "test" }],
handle: "test-product",
status: "draft",
is_giftcard: false,
options: [{ title: "Denominations" }],
profile_id: IdMap.getId("default_shipping_profile"),
@@ -170,6 +171,7 @@ describe("POST /admin/products", () => {
options: [{ title: "Denominations" }],
handle: "test-gift-card",
is_giftcard: true,
status: "draft",
profile_id: IdMap.getId("giftCardProfile"),
})
})
@@ -193,6 +193,9 @@ export default async (req, res) => {
.optional(),
thumbnail: Validator.string().optional(),
handle: Validator.string().optional(),
status: Validator.string()
.valid("proposed", "draft", "published", "rejected")
.default("draft"),
type: Validator.object()
.keys({
id: Validator.string().optional(),
@@ -1,4 +1,5 @@
import _ from "lodash"
import { MedusaError, Validator } from "medusa-core-utils"
import { defaultFields, defaultRelations } from "./"
/**
@@ -56,6 +57,20 @@ export default async (req, res) => {
selector.is_giftcard = req.query.is_giftcard === "true"
}
if ("status" in req.query) {
const schema = Validator.array()
.items(
Validator.string().valid("proposed", "draft", "published", "rejected")
)
.single()
const { value, error } = schema.validate(req.query.status)
if (value && !error) {
selector.status = value
}
}
const listConfig = {
select: includeFields.length ? includeFields : defaultFields,
relations: expandFields.length ? expandFields : defaultRelations,
@@ -193,6 +193,12 @@ export default async (req, res) => {
.allow(null, ""),
description: Validator.string().optional(),
discountable: Validator.boolean().optional(),
status: Validator.string().valid(
"proposed",
"draft",
"published",
"rejected"
),
type: Validator.object()
.keys({
id: Validator.string().optional(),
@@ -39,6 +39,7 @@ export default async (req, res) => {
const schema = Validator.object().keys({
value: Validator.string().required(),
label: Validator.string().required(),
parent_return_reason_id: Validator.string().optional(),
description: Validator.string()
.optional()
.allow(""),
@@ -0,0 +1,41 @@
/**
* @oas [delete] /return-reason/{id}
* operationId: "DeleteReturnReason"
* summary: "Delete a return reason"
* description: "Deletes a return reason."
* parameters:
* - (path) id=* {string} The id of the return reason
* tags:
* - Return Reason
* responses:
* 200:
* description: OK
* content:
* application/json:
* schema:
* properties:
* id:
* type: string
* description: The id of the deleted return reason
* object:
* type: string
* description: The type of the object that was deleted.
* deleted:
* type: boolean
*/
export default async (req, res) => {
const { id } = req.params
try {
const returnReasonService = req.scope.resolve("returnReasonService")
await returnReasonService.delete(id)
res.json({
id: id,
object: "return_reason",
deleted: true,
})
} catch (err) {
throw err
}
}
@@ -26,6 +26,11 @@ export default app => {
*/
route.post("/:id", middlewares.wrap(require("./update-reason").default))
/**
* Delete a reason
*/
route.delete("/:id", middlewares.wrap(require("./delete-reason").default))
return app
}
@@ -33,10 +38,14 @@ export const defaultFields = [
"id",
"value",
"label",
"parent_return_reason_id",
"description",
"created_at",
"updated_at",
"deleted_at",
]
export const defaultRelations = []
export const defaultRelations = [
"parent_return_reason",
"return_reason_children",
]
@@ -24,7 +24,7 @@ export default async (req, res) => {
try {
const returnReasonService = req.scope.resolve("returnReasonService")
const query = {}
const query = { parent_return_reason_id: null }
const data = await returnReasonService.list(query, {
select: defaultFields,
relations: defaultRelations,
@@ -42,6 +42,7 @@ export default async (req, res) => {
const schema = Validator.object().keys({
label: Validator.string().optional(),
parent_return_reason_id: Validator.string().optional(),
description: Validator.string()
.optional()
.allow(""),
@@ -138,18 +138,36 @@ export default async (req, res) => {
// If cart is part of swap, we register swap as complete
switch (cart.type) {
case "swap": {
const swapId = cart.metadata?.swap_id
let swap = await swapService
.withTransaction(manager)
.registerCartCompletion(swapId)
try {
const swapId = cart.metadata?.swap_id
let swap = await swapService
.withTransaction(manager)
.registerCartCompletion(swapId)
swap = await swapService
.withTransaction(manager)
.retrieve(swap.id, { relations: ["shipping_address"] })
swap = await swapService
.withTransaction(manager)
.retrieve(swap.id, { relations: ["shipping_address"] })
return {
response_code: 200,
response_body: { data: swap, type: "swap" },
return {
response_code: 200,
response_body: { data: swap, type: "swap" },
}
} catch (error) {
if (
error &&
error.code === MedusaError.Codes.INSUFFICIENT_INVENTORY
) {
return {
response_code: 409,
response_body: {
message: error.message,
type: error.type,
code: error.code,
},
}
} else {
throw error
}
}
}
// case "payment_link":
@@ -11,6 +11,7 @@ describe("POST /store/customers/:id", () => {
payload: {
first_name: "LeBron",
last_name: "James",
email: "test@email.com",
},
clientSession: {
jwt: {
@@ -31,6 +32,7 @@ describe("POST /store/customers/:id", () => {
{
first_name: "LeBron",
last_name: "James",
email: "test@email.com",
}
)
})
@@ -1,3 +1,4 @@
import { optional } from "joi"
import { Validator, MedusaError } from "medusa-core-utils"
import { defaultRelations, defaultFields } from "./"
@@ -27,6 +28,9 @@ import { defaultRelations, defaultFields } from "./"
* phone:
* description: "The Customer's phone number."
* type: string
* email:
* description: "The email of the customer."
* type: string
* metadata:
* description: "Metadata about the customer."
* type: object
@@ -51,6 +55,7 @@ export default async (req, res) => {
last_name: Validator.string().optional(),
password: Validator.string().optional(),
phone: Validator.string().optional(),
email: Validator.string().optional(),
metadata: Validator.object().optional(),
})
@@ -18,7 +18,7 @@ describe("GET /store/products", () => {
it("calls get product from productSerice", () => {
expect(ProductServiceMock.list).toHaveBeenCalledTimes(1)
expect(ProductServiceMock.list).toHaveBeenCalledWith(
{},
{ status: ["published"] },
{ relations: defaultRelations, skip: 0, take: 100 }
)
})
@@ -43,7 +43,7 @@ describe("GET /store/products", () => {
it("calls list from productSerice", () => {
expect(ProductServiceMock.list).toHaveBeenCalledTimes(1)
expect(ProductServiceMock.list).toHaveBeenCalledWith(
{ is_giftcard: true },
{ is_giftcard: true, status: ["published"] },
{ relations: defaultRelations, skip: 0, take: 100 }
)
})
@@ -1,3 +1,4 @@
import { MedusaError, Validator } from "medusa-core-utils"
import { defaultRelations } from "."
/**
@@ -41,6 +42,8 @@ export default async (req, res) => {
selector.is_giftcard = req.query.is_giftcard === "true"
}
selector.status = ["published"]
const listConfig = {
relations: defaultRelations,
skip: offset,
@@ -23,10 +23,14 @@ export const defaultFields = [
"id",
"value",
"label",
"parent_return_reason_id",
"description",
"created_at",
"updated_at",
"deleted_at",
]
export const defaultRelations = []
export const defaultRelations = [
"parent_return_reason",
"return_reason_children",
]
@@ -24,7 +24,7 @@ export default async (req, res) => {
try {
const returnReasonService = req.scope.resolve("returnReasonService")
const query = {}
const query = { parent_return_reason_id: null}
const data = await returnReasonService.list(query, {
select: defaultFields,
relations: defaultRelations,
@@ -28,10 +28,7 @@ describe("Get variant by id", () => {
describe("get variant with prices", () => {
let subject
beforeAll(async () => {
subject = await request(
"GET",
`/store/variants/${IdMap.getId("variantWithPrices")}`
)
subject = await request("GET", `/store/variants/variant_with_prices`)
})
it("successfully retrieves variants with prices", async () => {
expect(subject.status).toEqual(200)
+3
View File
@@ -114,6 +114,9 @@ const t = async function({ directory, migrate, seedFile }) {
const variants = p.variants
delete p.variants
// default to the products being visible
p.status = p.status || "published"
p.profile_id = defaultProfile.id
if (p.is_giftcard) {
p.profile_id = gcProfile.id
+1
View File
@@ -45,4 +45,5 @@ export { Swap } from "./models/swap"
export { User } from "./models/user"
export { DraftOrder } from "./models/draft-order"
export { ReturnReason } from "./models/return-reason"
export { Note } from "./models/note"
export { RMAShippingOption } from "./models/rma-shipping-option"
@@ -0,0 +1,24 @@
import {MigrationInterface, QueryRunner} from "typeorm";
export class allowBackorderSwaps1630505790603 implements MigrationInterface {
name = 'allowBackorderSwaps1630505790603'
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`ALTER TABLE "swap" ADD "allow_backorder" boolean NOT NULL DEFAULT false`);
await queryRunner.query(`ALTER TABLE "cart" ADD "payment_authorized_at" TIMESTAMP WITH TIME ZONE`);
await queryRunner.query(`ALTER TYPE "swap_payment_status_enum" RENAME TO "swap_payment_status_enum_old"`);
await queryRunner.query(`CREATE TYPE "swap_payment_status_enum" AS ENUM('not_paid', 'awaiting', 'captured', 'confirmed', 'canceled', 'difference_refunded', 'partially_refunded', 'refunded', 'requires_action')`);
await queryRunner.query(`ALTER TABLE "swap" ALTER COLUMN "payment_status" TYPE "swap_payment_status_enum" USING "payment_status"::"text"::"swap_payment_status_enum"`);
await queryRunner.query(`DROP TYPE "swap_payment_status_enum_old"`);
}
public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`CREATE TYPE "swap_payment_status_enum_old" AS ENUM('not_paid', 'awaiting', 'captured', 'canceled', 'difference_refunded', 'partially_refunded', 'refunded', 'requires_action')`);
await queryRunner.query(`ALTER TABLE "swap" ALTER COLUMN "payment_status" TYPE "swap_payment_status_enum_old" USING "payment_status"::"text"::"swap_payment_status_enum_old"`);
await queryRunner.query(`DROP TYPE "swap_payment_status_enum"`);
await queryRunner.query(`ALTER TYPE "swap_payment_status_enum_old" RENAME TO "swap_payment_status_enum"`);
await queryRunner.query(`ALTER TABLE "cart" DROP COLUMN "payment_authorized_at"`);
await queryRunner.query(`ALTER TABLE "swap" DROP COLUMN "allow_backorder"`);
}
}
@@ -0,0 +1,14 @@
import {MigrationInterface, QueryRunner} from "typeorm";
export class validDurationForDiscount1631696624528 implements MigrationInterface {
name = 'validDurationForDiscount1631696624528'
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`ALTER TABLE "discount" ADD "valid_duration" character varying`);
}
public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`ALTER TABLE "discount" DROP COLUMN "valid_duration"`);
}
}
@@ -0,0 +1,20 @@
import {MigrationInterface, QueryRunner} from "typeorm";
export class nestedReturnReasons1631800727788 implements MigrationInterface {
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(
`ALTER TABLE "return_reason" ADD "parent_return_reason_id" character varying`
)
await queryRunner.query(`ALTER TABLE "return_reason" ADD CONSTRAINT "FK_2250c5d9e975987ab212f61a657" FOREIGN KEY ("parent_return_reason_id") REFERENCES "return_reason"("id") ON DELETE NO ACTION ON UPDATE NO ACTION`);
}
public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(
`ALTER TABLE "return_reason" DROP COLUMN "parent_return_reason_id"`
)
}
}
@@ -0,0 +1,28 @@
import { MigrationInterface, QueryRunner } from "typeorm"
export class statusOnProduct1631864388026 implements MigrationInterface {
name = "statusOnProduct1631864388026"
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(
`CREATE TYPE "product_status_enum" AS ENUM('draft', 'proposed', 'published', 'rejected')`
)
await queryRunner.query(
`ALTER TABLE "product" ADD "status" "product_status_enum" `
)
await queryRunner.query(
`UPDATE "product" SET "status" = 'published' WHERE "status" IS NULL`
)
await queryRunner.query(
`ALTER TABLE "product" ALTER COLUMN "status" SET NOT NULL`
)
await queryRunner.query(
`ALTER TABLE "product" ALTER COLUMN "status" SET DEFAULT 'draft'`
)
}
public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`ALTER TABLE "product" DROP COLUMN "status"`)
await queryRunner.query(`DROP TYPE "product_status_enum"`)
}
}
@@ -0,0 +1,23 @@
import { MigrationInterface, QueryRunner } from "typeorm"
export class addNotes1632220294687 implements MigrationInterface {
name = "addNotes1632220294687"
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(
`CREATE TABLE "note" ("id" character varying NOT NULL, "value" character varying NOT NULL, "resource_type" character varying NOT NULL, "resource_id" character varying NOT NULL, "author_id" character varying, "created_at" TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now(), "updated_at" TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now(), "deleted_at" TIMESTAMP WITH TIME ZONE, "metadata" jsonb, CONSTRAINT "PK_96d0c172a4fba276b1bbed43058" PRIMARY KEY ("id"))`
)
await queryRunner.query(
`CREATE INDEX "IDX_f74980b411cf94af523a72af7d" ON "note" ("resource_type") `
)
await queryRunner.query(
`CREATE INDEX "IDX_3287f98befad26c3a7dab088cf" ON "note" ("resource_id") `
)
}
public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`DROP INDEX "IDX_3287f98befad26c3a7dab088cf"`)
await queryRunner.query(`DROP INDEX "IDX_f74980b411cf94af523a72af7d"`)
await queryRunner.query(`DROP TABLE "note"`)
}
}
+3
View File
@@ -231,6 +231,9 @@ export class Cart {
@Column({ type: resolveDbType("timestamptz"), nullable: true })
completed_at: Date
@Column({ type: resolveDbType("timestamptz"), nullable: true })
payment_authorized_at: Date
@CreateDateColumn({ type: resolveDbType("timestamptz") })
created_at: Date
+1 -1
View File
@@ -46,7 +46,7 @@ export class Customer {
)
shipping_addresses: Address[]
@Column({ nullable: true })
@Column({ nullable: true, select: false })
password_hash: string
@Column({ nullable: true })
+4 -1
View File
@@ -44,7 +44,7 @@ export class Discount {
@Column({ nullable: true })
parent_discount_id: string
@ManyToOne(() => Discount)
@JoinColumn({ name: "parent_discount_id" })
parent_discount: Discount
@@ -58,6 +58,9 @@ export class Discount {
@Column({ type: resolveDbType("timestamptz"), nullable: true })
ends_at: Date
@Column({ nullable: true })
valid_duration: string
@ManyToMany(() => Region, { cascade: true })
@JoinTable({
name: "discount_regions",
+96
View File
@@ -0,0 +1,96 @@
import {
Entity,
BeforeInsert,
Column,
DeleteDateColumn,
CreateDateColumn,
UpdateDateColumn,
Index,
JoinColumn,
PrimaryColumn,
ManyToOne,
} from "typeorm"
import { ulid } from "ulid"
import { User } from "./user"
import { resolveDbType, DbAwareColumn } from "../utils/db-aware-column"
@Entity()
export class Note {
@PrimaryColumn()
id: string
@Column()
value: string
@Index()
@Column()
resource_type: string
@Index()
@Column()
resource_id: string
@Column({ nullable: true })
author_id: string
@ManyToOne(() => User)
@JoinColumn({ name: "author_id" })
author: User
@CreateDateColumn({ type: resolveDbType("timestamptz") })
created_at: Date
@UpdateDateColumn({ type: resolveDbType("timestamptz") })
updated_at: Date
@DeleteDateColumn({ type: resolveDbType("timestamptz") })
deleted_at: Date
@DbAwareColumn({ type: "jsonb", nullable: true })
metadata: any
@BeforeInsert()
private beforeInsert() {
if (this.id) return
const id = ulid()
this.id = `note_${id}`
}
}
/**
* @schema note
* title: "Note"
* description: "Notes are elements which we can use in association with different resources to allow users to describe additional information in relation to these."
* x-resourceId: note
* properties:
* id:
* description: "The id of the Note. This value will be prefixed by `note_`."
* type: string
* resource_type:
* description: "The type of resource that the Note refers to."
* type: string
* resource_id:
* description: "The id of the resource that the Note refers to."
* type: string
* value:
* description: "The contents of the note."
* type: string
* author:
* description: "The author of the note."
* type: User
* created_at:
* description: "The date with timezone at which the resource was created."
* type: string
* format: date-time
* updated_at:
* description: "The date with timezone at which the resource was last updated."
* type: string
* format: date-time
* deleted_at:
* description: "The date with timezone at which the resource was deleted."
* type: string
* format: date-time
* metadata:
* description: "An optional key-value map with additional information."
* type: object
*/
+10
View File
@@ -26,6 +26,13 @@ import { ProductVariant } from "./product-variant"
import { ShippingProfile } from "./shipping-profile"
import _ from "lodash"
export enum Status {
DRAFT = "draft",
PROPOSED = "proposed",
PUBLISHED = "published",
REJECTED = "rejected",
}
@Entity()
export class Product {
@PrimaryColumn()
@@ -47,6 +54,9 @@ export class Product {
@Column({ default: false })
is_giftcard: boolean
@DbAwareColumn({ type: "enum", enum: Status, default: "draft" })
status: Status
@ManyToMany(() => Image, { cascade: ["insert"] })
@JoinTable({
name: "product_images",
@@ -7,6 +7,9 @@ import {
CreateDateColumn,
UpdateDateColumn,
PrimaryColumn,
ManyToOne,
OneToMany,
JoinColumn
} from "typeorm"
import { ulid } from "ulid"
import { resolveDbType, DbAwareColumn } from "../utils/db-aware-column"
@@ -26,6 +29,21 @@ export class ReturnReason {
@Column({ nullable: true })
description: string
@Column({ nullable: true })
parent_return_reason_id: string
@ManyToOne(() => ReturnReason, {cascade: ['soft-remove']}
)
@JoinColumn({ name: "parent_return_reason_id" })
parent_return_reason: ReturnReason
@OneToMany(
() => ReturnReason,
return_reason => return_reason.parent_return_reason,
{ cascade: ["insert", 'soft-remove'] }
)
return_reason_children: ReturnReason[]
@CreateDateColumn({ type: resolveDbType("timestamptz") })
created_at: Date
+7
View File
@@ -39,6 +39,7 @@ export enum PaymentStatus {
NOT_PAID = "not_paid",
AWAITING = "awaiting",
CAPTURED = "captured",
CONFIRMED = "confirmed",
CANCELED = "canceled",
DIFFERENCE_REFUNDED = "difference_refunded",
PARTIALLY_REFUNDED = "partially_refunded",
@@ -145,6 +146,9 @@ export class Swap {
@Column({ type: "boolean", nullable: true })
no_notification: Boolean
@Column({ type: "boolean", default: false })
allow_backorder: Boolean
@DbAwareColumn({ type: "jsonb", nullable: true })
metadata: any
@@ -227,6 +231,9 @@ export class Swap {
* cart_id:
* description: "The id of the Cart that the Customer will use to confirm the Swap."
* type: string
* allow_backorder:
* description: "If true, swaps can be completed with items out of stock"
* type: boolean
* confirmed_at:
* description: "The date with timezone at which the Swap was confirmed by the Customer."
* type: string
+1 -1
View File
@@ -26,7 +26,7 @@ export class User {
@Column({ nullable: true })
last_name: string
@Column({ nullable: true })
@Column({ nullable: true, select: false })
password_hash: string
@Column({ nullable: true })
+5
View File
@@ -0,0 +1,5 @@
import { EntityRepository, Repository } from "typeorm"
import { Note } from "../models/note"
@EntityRepository(Note)
export class NoteRepository extends Repository<Note> {}
@@ -223,7 +223,7 @@ export const ProductVariantServiceMock = {
if (variantId === "4") {
return Promise.resolve(variant4)
}
if (variantId === IdMap.getId("variantWithPrices")) {
if (variantId === "variant_with_prices") {
return Promise.resolve(variantWithPrices)
}
if (variantId === IdMap.getId("validId")) {
@@ -1083,6 +1083,9 @@ describe("CartService", () => {
id: "region",
countries: [{ iso_2: "us" }],
},
shipping_address: {
country_code: "us",
},
items: [IdMap.getId("testitem"), null],
payment_session: null,
payment_sessions: [],
@@ -1538,6 +1541,12 @@ describe("CartService", () => {
})
describe("applyDiscount", () => {
const getOffsetDate = offset => {
const date = new Date()
date.setDate(date.getDate() + offset)
return date
}
const cartRepository = MockRepository({
findOneWithRelations: (rels, q) => {
if (q.where.id === IdMap.getId("with-d")) {
@@ -1605,6 +1614,69 @@ describe("CartService", () => {
},
})
}
if (code === "EarlyDiscount") {
return Promise.resolve({
id: IdMap.getId("10off"),
code: "10%OFF",
regions: [{ id: IdMap.getId("good") }],
rule: {
type: "percentage",
},
starts_at: getOffsetDate(1),
ends_at: getOffsetDate(10),
})
}
if (code === "ExpiredDiscount") {
return Promise.resolve({
id: IdMap.getId("10off"),
code: "10%OFF",
regions: [{ id: IdMap.getId("good") }],
rule: {
type: "percentage",
},
ends_at: getOffsetDate(-1),
starts_at: getOffsetDate(-10),
})
}
if (code === "ExpiredDynamicDiscount") {
return Promise.resolve({
id: IdMap.getId("10off"),
code: "10%OFF",
is_dynamic: true,
regions: [{ id: IdMap.getId("good") }],
rule: {
type: "percentage",
},
starts_at: getOffsetDate(-10),
ends_at: getOffsetDate(-1),
})
}
if (code === "ExpiredDynamicDiscountEndDate") {
return Promise.resolve({
id: IdMap.getId("10off"),
is_dynamic: true,
code: "10%OFF",
regions: [{ id: IdMap.getId("good") }],
rule: {
type: "percentage",
},
starts_at: getOffsetDate(-10),
ends_at: getOffsetDate(-3),
valid_duration: "P0Y0M1D",
})
}
if (code === "ValidDiscount") {
return Promise.resolve({
id: IdMap.getId("10off"),
code: "10%OFF",
regions: [{ id: IdMap.getId("good") }],
rule: {
type: "percentage",
},
starts_at: getOffsetDate(-10),
ends_at: getOffsetDate(10),
})
}
return Promise.resolve({
id: IdMap.getId("10off"),
code: "10%OFF",
@@ -1755,6 +1827,76 @@ describe("CartService", () => {
})
})
it("successfully applies valid discount with expiration date to cart", async () => {
await cartService.update(IdMap.getId("fr-cart"), {
discounts: [
{
code: "ValidDiscount",
},
],
})
expect(eventBusService.emit).toHaveBeenCalledTimes(1)
expect(eventBusService.emit).toHaveBeenCalledWith(
"cart.updated",
expect.any(Object)
)
expect(cartRepository.save).toHaveBeenCalledTimes(1)
expect(cartRepository.save).toHaveBeenCalledWith({
id: IdMap.getId("cart"),
region_id: IdMap.getId("good"),
discount_total: 0,
shipping_total: 0,
subtotal: 0,
tax_total: 0,
total: 0,
discounts: [
{
id: IdMap.getId("10off"),
code: "10%OFF",
regions: [{ id: IdMap.getId("good") }],
rule: {
type: "percentage",
},
starts_at: expect.any(Date),
ends_at: expect.any(Date),
},
],
})
})
it("throws if discount is applied too before it's valid", async () => {
await expect(
cartService.update(IdMap.getId("cart"), {
discounts: [{ code: "EarlyDiscount" }],
})
).rejects.toThrow("Discount is not valid yet")
})
it("throws if expired discount is applied", async () => {
await expect(
cartService.update(IdMap.getId("cart"), {
discounts: [{ code: "ExpiredDiscount" }],
})
).rejects.toThrow("Discount is expired")
})
it("throws if expired dynamic discount is applied", async () => {
await expect(
cartService.update(IdMap.getId("cart"), {
discounts: [{ code: "ExpiredDynamicDiscount" }],
})
).rejects.toThrow("Discount is expired")
})
it("throws if expired dynamic discount is applied after ends_at", async () => {
await expect(
cartService.update(IdMap.getId("cart"), {
discounts: [{ code: "ExpiredDynamicDiscountEndDate" }],
})
).rejects.toThrow("Discount is expired")
})
it("throws if discount limit is reached", async () => {
await expect(
cartService.update(IdMap.getId("cart"), {
@@ -1,5 +1,4 @@
import { IdMap, MockManager, MockRepository } from "medusa-test-utils"
import { add } from "winston"
import CustomerService from "../customer"
const eventBusService = {
@@ -55,6 +55,74 @@ describe("DiscountService", () => {
expect(discountRepository.save).toHaveBeenCalledTimes(1)
})
it("successfully creates discount with start and end dates", async () => {
await discountService.create({
code: "test",
rule: {
type: "percentage",
allocation: "total",
value: 20,
},
starts_at: new Date("03/14/2021"),
ends_at: new Date("03/15/2021"),
regions: [IdMap.getId("france")],
})
expect(discountRuleRepository.create).toHaveBeenCalledTimes(1)
expect(discountRuleRepository.create).toHaveBeenCalledWith({
type: "percentage",
allocation: "total",
value: 20,
})
expect(discountRuleRepository.save).toHaveBeenCalledTimes(1)
expect(discountRepository.create).toHaveBeenCalledTimes(1)
expect(discountRepository.create).toHaveBeenCalledWith({
code: "TEST",
rule: expect.anything(),
regions: [{ id: IdMap.getId("france") }],
starts_at: new Date("03/14/2021"),
ends_at: new Date("03/15/2021"),
})
expect(discountRepository.save).toHaveBeenCalledTimes(1)
})
it("successfully creates discount with start date and a valid duration", async () => {
await discountService.create({
code: "test",
rule: {
type: "percentage",
allocation: "total",
value: 20,
},
starts_at: new Date("03/14/2021"),
valid_duration: "P0Y0M1D",
regions: [IdMap.getId("france")],
})
expect(discountRuleRepository.create).toHaveBeenCalledTimes(1)
expect(discountRuleRepository.create).toHaveBeenCalledWith({
type: "percentage",
allocation: "total",
value: 20,
})
expect(discountRuleRepository.save).toHaveBeenCalledTimes(1)
expect(discountRepository.create).toHaveBeenCalledTimes(1)
expect(discountRepository.create).toHaveBeenCalledWith({
code: "TEST",
rule: expect.anything(),
regions: [{ id: IdMap.getId("france") }],
starts_at: new Date("03/14/2021"),
valid_duration: "P0Y0M1D",
})
expect(discountRepository.save).toHaveBeenCalledTimes(1)
})
})
describe("retrieve", () => {
@@ -376,6 +444,7 @@ describe("DiscountService", () => {
id: "parent",
is_dynamic: true,
rule_id: "parent_rule",
valid_duration: "P1Y",
}),
})
@@ -412,6 +481,8 @@ describe("DiscountService", () => {
rule_id: "parent_rule",
parent_discount_id: "parent",
code: "HI",
usage_limit: undefined,
ends_at: expect.any(Date),
})
})
})
@@ -494,9 +565,6 @@ describe("DiscountService", () => {
expect(discountRepository.findAndCount).toHaveBeenCalledTimes(1)
expect(discountRepository.findAndCount).toHaveBeenCalledWith({
join: {
alias: "discount",
},
where: expect.anything(),
skip: 0,
take: 50,
@@ -0,0 +1,202 @@
import NoteService from "../note"
import { MockManager, MockRepository, IdMap } from "medusa-test-utils"
import { EventBusServiceMock } from "../__mocks__/event-bus"
describe("NoteService", () => {
describe("list", () => {
const noteRepo = MockRepository({
find: q => {
return Promise.resolve([
{ id: IdMap.getId("note"), value: "some note" },
])
},
})
const noteService = new NoteService({
manager: MockManager,
noteRepository: noteRepo,
})
beforeAll(async () => {
jest.clearAllMocks()
})
it("calls note model functions", async () => {
await noteService.list(
{ resource_id: IdMap.getId("note") },
{
relations: ["author"],
}
)
expect(noteRepo.find).toHaveBeenCalledTimes(1)
expect(noteRepo.find).toHaveBeenCalledWith({
where: {
resource_id: IdMap.getId("note"),
},
relations: ["author"],
})
})
})
describe("retrieve", () => {
const noteRepo = MockRepository({
findOne: q => {
switch (q.where.id) {
case IdMap.getId("note"):
return Promise.resolve({
id: IdMap.getId("note"),
value: "some note",
})
default:
return Promise.resolve()
}
},
})
const noteService = new NoteService({
manager: MockManager,
noteRepository: noteRepo,
})
beforeAll(async () => {
jest.clearAllMocks()
})
it("calls note model functions", async () => {
await noteService.retrieve(IdMap.getId("note"), { relations: ["author"] })
expect(noteRepo.findOne).toHaveBeenCalledTimes(1)
expect(noteRepo.findOne).toHaveBeenCalledWith({
where: { id: IdMap.getId("note") },
relations: ["author"],
})
})
it("fails when note is not found", async () => {
await expect(
noteService.retrieve(IdMap.getId("not-existing"))
).rejects.toThrow(
`Note with id: ${IdMap.getId("not-existing")} was not found.`
)
})
})
describe("create", () => {
const note = {
id: IdMap.getId("note"),
author_id: IdMap.getId("user"),
}
const noteRepo = MockRepository({
create: f => Promise.resolve(note),
save: f => Promise.resolve(note),
})
const noteService = new NoteService({
manager: MockManager,
noteRepository: noteRepo,
eventBusService: EventBusServiceMock,
})
beforeAll(async () => {
jest.clearAllMocks()
})
it("calls note model functions", async () => {
await noteService.create({
resource_id: IdMap.getId("resource-id"),
resource_type: "type",
value: "my note",
author_id: IdMap.getId("user"),
})
expect(noteRepo.create).toHaveBeenCalledTimes(1)
expect(noteRepo.create).toHaveBeenCalledWith({
resource_id: IdMap.getId("resource-id"),
resource_type: "type",
value: "my note",
author_id: IdMap.getId("user"),
metadata: {},
})
expect(noteRepo.save).toHaveBeenCalledTimes(1)
expect(noteRepo.save).toHaveBeenCalledWith({
id: IdMap.getId("note"),
author_id: IdMap.getId("user"),
})
expect(EventBusServiceMock.emit).toHaveBeenCalledTimes(1)
expect(EventBusServiceMock.emit).toHaveBeenCalledWith(
NoteService.Events.CREATED,
{ id: IdMap.getId("note") }
)
})
})
describe("update", () => {
const note = { id: IdMap.getId("note") }
const noteRepo = MockRepository({
findOne: f => Promise.resolve(note),
save: f => Promise.resolve(note),
})
const noteService = new NoteService({
manager: MockManager,
noteRepository: noteRepo,
eventBusService: EventBusServiceMock,
})
beforeAll(async () => {
jest.clearAllMocks()
})
it("calls note model functions", async () => {
await noteService.update(IdMap.getId("note"), "new note")
expect(noteRepo.save).toHaveBeenCalledTimes(1)
expect(noteRepo.save).toHaveBeenCalledWith({
...note,
value: "new note",
})
expect(EventBusServiceMock.emit).toHaveBeenCalledTimes(1)
expect(EventBusServiceMock.emit).toHaveBeenCalledWith(
NoteService.Events.UPDATED,
{ id: IdMap.getId("note") }
)
})
})
describe("delete", () => {
const note = { id: IdMap.getId("note") }
const noteRepo = MockRepository({
softRemove: f => Promise.resolve(),
findOne: f => Promise.resolve(note),
})
const noteService = new NoteService({
manager: MockManager,
noteRepository: noteRepo,
eventBusService: EventBusServiceMock,
})
beforeAll(async () => {
jest.clearAllMocks()
})
it("calls note model functions", async () => {
await noteService.delete(IdMap.getId("note"))
expect(noteRepo.softRemove).toHaveBeenCalledTimes(1)
expect(noteRepo.softRemove).toHaveBeenCalledWith(note)
expect(EventBusServiceMock.emit).toHaveBeenCalledTimes(1)
expect(EventBusServiceMock.emit).toHaveBeenCalledWith(
NoteService.Events.DELETED,
{ id: IdMap.getId("note") }
)
})
})
})
@@ -203,6 +203,7 @@ describe("OrderService", () => {
}
orderService.cartService_.retrieve = jest.fn(() => Promise.resolve(cart))
orderService.cartService_.update = jest.fn(() => Promise.resolve())
await orderService.createFromCart("cart_id")
const order = {
@@ -305,6 +306,7 @@ describe("OrderService", () => {
orderService.cartService_.retrieve = () => {
return Promise.resolve(cart)
}
orderService.cartService_.update = () => Promise.resolve()
await orderService.createFromCart("cart_id")
const order = {
@@ -453,6 +455,7 @@ describe("OrderService", () => {
total: 100,
}
orderService.cartService_.retrieve = () => Promise.resolve(cart)
orderService.cartService_.update = () => Promise.resolve()
const res = orderService.createFromCart(cart)
await expect(res).rejects.toThrow(
"Variant with id: variant-1 does not have the required inventory"
@@ -199,6 +199,12 @@ describe("ProductService", () => {
variants: [{ id: IdMap.getId("green"), title: "Green" }],
})
}
if (query.where.id === "prod_status") {
return Promise.resolve({
id: "prod_status",
status: "draft",
})
}
if (query.where.id === "123") {
return undefined
}
@@ -290,6 +296,18 @@ describe("ProductService", () => {
expect(productRepository.save).toHaveBeenCalledTimes(1)
})
it("successfully updates product status", async () => {
await productService.update(IdMap.getId("ironman"), {
status: "published",
})
expect(productRepository.save).toHaveBeenCalledTimes(1)
expect(productRepository.save).toHaveBeenCalledWith({
id: IdMap.getId("ironman"),
status: "published",
})
})
it("successfully updates product", async () => {
await productService.update(IdMap.getId("ironman"), {
title: "Full suit",
@@ -272,11 +272,7 @@ describe("ShippingOptionService", () => {
amount: 10,
})
expect(shippingOptionRequirementRepository.create).toBeCalledTimes(1)
expect(shippingOptionRequirementRepository.create).toBeCalledWith({
type: "max_subtotal",
amount: 10,
})
expect(shippingOptionRequirementRepository.create).toBeCalledTimes(0)
expect(shippingOptionRepository.save).toBeCalledTimes(1)
expect(shippingOptionRepository.save).toBeCalledWith({
@@ -747,6 +747,13 @@ describe("SwapService", () => {
},
}
const cartService = {
update: jest.fn(),
withTransaction: function() {
return this
},
}
const swapRepo = MockRepository({
findOneWithRelations: () => Promise.resolve(existing),
})
@@ -758,6 +765,7 @@ describe("SwapService", () => {
lineItemService,
eventBusService,
fulfillmentService,
cartService,
})
it("creates a shipment", async () => {
@@ -837,6 +845,15 @@ describe("SwapService", () => {
},
}
const cartService = {
update: () => {
return Promise.resolve()
},
withTransaction: function() {
return this
},
}
const paymentProviderService = {
getStatus: jest.fn(() => {
return Promise.resolve("authorized")
@@ -844,6 +861,9 @@ describe("SwapService", () => {
updatePayment: jest.fn(() => {
return Promise.resolve()
}),
cancelPayment: jest.fn(() => {
return Promise.resolve()
}),
withTransaction: function() {
return this
},
@@ -878,6 +898,7 @@ describe("SwapService", () => {
eventBusService,
swapRepository: swapRepo,
totalsService,
cartService,
paymentProviderService,
eventBusService,
shippingOptionService,
@@ -939,6 +960,7 @@ describe("SwapService", () => {
eventBusService,
swapRepository: swapRepo,
totalsService,
cartService,
paymentProviderService,
eventBusService,
shippingOptionService,
+17 -5
View File
@@ -76,12 +76,17 @@ class AuthService extends BaseService {
*/
async authenticate(email, password) {
try {
const user = await this.userService_.retrieveByEmail(email)
const userPasswordHash = await this.userService_.retrieveByEmail(email, {
select: ["password_hash"],
})
const passwordsMatch = await this.comparePassword_(
password,
user.password_hash
userPasswordHash.password_hash
)
if (passwordsMatch) {
const user = await this.userService_.retrieveByEmail(email)
return {
success: true,
user,
@@ -113,8 +118,13 @@ class AuthService extends BaseService {
*/
async authenticateCustomer(email, password) {
try {
const customer = await this.customerService_.retrieveByEmail(email)
if (!customer.password_hash) {
const customerPasswordHash = await this.customerService_.retrieveByEmail(
email,
{
select: ["password_hash"],
}
)
if (!customerPasswordHash.password_hash) {
return {
success: false,
error: "Invalid email or password",
@@ -123,9 +133,11 @@ class AuthService extends BaseService {
const passwordsMatch = await this.comparePassword_(
password,
customer.password_hash
customerPasswordHash.password_hash
)
if (passwordsMatch) {
const customer = await this.customerService_.retrieveByEmail(email)
return {
success: true,
customer,
+34 -4
View File
@@ -683,6 +683,14 @@ class CartService extends BaseService {
}
}
if ("completed_at" in update) {
cart.completed_at = update.completed_at
}
if ("payment_authorized_at" in update) {
cart.payment_authorized_at = update.payment_authorized_at
}
const result = await cartRepo.save(cart)
if ("email" in update || "customer_id" in update) {
@@ -880,6 +888,21 @@ class CartService extends BaseService {
)
}
const today = new Date()
if (discount.starts_at > today) {
throw new MedusaError(
MedusaError.Types.NOT_ALLOWED,
"Discount is not valid yet"
)
}
if (discount.ends_at && discount.ends_at < today) {
throw new MedusaError(
MedusaError.Types.NOT_ALLOWED,
"Discount is expired"
)
}
let regions = discount.regions
if (discount.parent_discount_id) {
const parent = await this.discountService_.retrieve(
@@ -1028,7 +1051,7 @@ class CartService extends BaseService {
// If cart total is 0, we don't perform anything payment related
if (cart.total <= 0) {
cart.completed_at = new Date()
cart.payment_authorized_at = new Date()
return cartRepository.save(cart)
}
@@ -1047,7 +1070,7 @@ class CartService extends BaseService {
.createPayment(freshCart)
freshCart.payment = payment
freshCart.completed_at = new Date()
freshCart.payment_authorized_at = new Date()
}
const updated = await cartRepository.save(freshCart)
@@ -1386,7 +1409,7 @@ class CartService extends BaseService {
* @return {Promise} the result of the update operation
*/
async setRegion_(cart, regionId, countryCode) {
if (cart.completed_at) {
if (cart.completed_at || cart.payment_authorized_at) {
throw new MedusaError(
MedusaError.Types.NOT_ALLOWED,
"Cannot change the region of a completed cart"
@@ -1472,7 +1495,7 @@ class CartService extends BaseService {
}
}
await addrRepo.save(updated)
await this.updateShippingAddress_(cart, updated, addrRepo)
}
// Shipping methods are determined by region so the user needs to find a
@@ -1528,6 +1551,13 @@ class CartService extends BaseService {
)
}
if (cart.payment_authorized_at) {
throw new MedusaError(
MedusaError.Types.NOT_ALLOWED,
"Can't delete a cart with an authorized payment"
)
}
const cartRepo = manager.getCustomRepository(this.cartRepository_)
return cartRepo.remove(cartId)
})
+7 -11
View File
@@ -3,7 +3,7 @@ import Scrypt from "scrypt-kdf"
import _ from "lodash"
import { Validator, MedusaError } from "medusa-core-utils"
import { BaseService } from "medusa-interfaces"
import { Brackets } from "typeorm"
import { Brackets, ILike } from "typeorm"
/**
* Provides layer to manipulate customers.
@@ -149,9 +149,9 @@ class CustomerService extends BaseService {
qb.andWhere(
new Brackets(qb => {
qb.where(`email ILIKE :q`, { q: `%${q}%` })
.orWhere(`first_name ILIKE :q`, { q: `%${q}%` })
.orWhere(`last_name ILIKE :q`, { q: `%${q}%` })
qb.where({ email: ILike(`%${q}%`) })
.orWhere({ first_name: ILike(`%${q}%`) })
.orWhere({ last_name: ILike(`%${q}%`) })
})
)
}
@@ -183,18 +183,14 @@ class CustomerService extends BaseService {
delete where.first_name
delete where.last_name
query.join = {
alias: "customer",
}
query.where = qb => {
qb.where(where)
qb.andWhere(
new Brackets(qb => {
qb.where(`customer.first_name ILIKE :q`, { q: `%${q}%` })
.orWhere(`customer.last_name ILIKE :q`, { q: `%${q}%` })
.orWhere(`customer.email ILIKE :q`, { q: `%${q}%` })
qb.where({ email: ILike(`%${q}%`) })
.orWhere({ first_name: ILike(`%${q}%`) })
.orWhere({ last_name: ILike(`%${q}%`) })
})
)
}
+20 -6
View File
@@ -2,7 +2,9 @@ import _ from "lodash"
import randomize from "randomatic"
import { BaseService } from "medusa-interfaces"
import { Validator, MedusaError } from "medusa-core-utils"
import { Brackets } from "typeorm"
import { MedusaErrorCodes } from "medusa-core-utils/dist/errors"
import { parse, toSeconds } from "iso8601-duration"
import { Brackets, ILike } from "typeorm"
/**
* Provides layer to manipulate discounts.
@@ -151,16 +153,12 @@ class DiscountService extends BaseService {
delete where.code
query.join = {
alias: "discount",
}
query.where = qb => {
qb.where(where)
qb.andWhere(
new Brackets(qb => {
qb.where(`discount.code ILIKE :q`, { q: `%${q}%` })
qb.where({ code: ILike(`%${q}%`) })
})
)
}
@@ -274,6 +272,15 @@ class DiscountService extends BaseService {
const { rule, metadata, regions, ...rest } = update
if (rest.ends_at) {
if (discount.starts_at >= new Date(update.ends_at)) {
throw new MedusaError(
MedusaError.Types.INVALID_DATA,
`"ends_at" must be greater than "starts_at"`
)
}
}
if (regions) {
discount.regions = await Promise.all(
regions.map(regionId => this.regionService_.retrieve(regionId))
@@ -333,6 +340,13 @@ class DiscountService extends BaseService {
usage_limit: discount.usage_limit,
}
if (discount.valid_duration) {
const lastValidDate = new Date()
lastValidDate.setSeconds(
lastValidDate.getSeconds() + toSeconds(parse(discount.valid_duration))
)
toCreate.ends_at = lastValidDate
}
const created = await discountRepo.create(toCreate)
const result = await discountRepo.save(created)
return result
+1 -1
View File
@@ -1,7 +1,7 @@
import _ from "lodash"
import { BaseService } from "medusa-interfaces"
import { MedusaError } from "medusa-core-utils"
import { Brackets } from "typeorm"
import { Brackets, ILike } from "typeorm"
/**
* Handles draft orders
+3 -4
View File
@@ -1,8 +1,7 @@
import _ from "lodash"
import randomize from "randomatic"
import { BaseService } from "medusa-interfaces"
import { Brackets } from "typeorm"
import { MedusaError } from "medusa-core-utils"
import { BaseService } from "medusa-interfaces"
import randomize from "randomatic"
import { Brackets } from "typeorm"
/**
* Provides layer to manipulate gift cards.
+169
View File
@@ -0,0 +1,169 @@
import { MedusaError } from "medusa-core-utils"
import { BaseService } from "medusa-interfaces"
import _ from "lodash"
import { TransactionManager } from "typeorm"
class NoteService extends BaseService {
static Events = {
CREATED: "note.created",
UPDATED: "note.updated",
DELETED: "note.deleted",
}
constructor({ manager, noteRepository, eventBusService, userService }) {
super()
/** @private @const {EntityManager} */
this.manager_ = manager
/** @private @const {NoteRepository} */
this.noteRepository_ = noteRepository
/** @private @const {EventBus} */
this.eventBus_ = eventBusService
}
/**
* Sets the service's manager to a given transaction manager
* @param {EntityManager} transactionManager - the manager to use
* @return {NoteService} a cloned note service
*/
withTransaction(transactionManager) {
if (!TransactionManager) {
return this
}
const cloned = new NoteService({
manager: transactionManager,
noteRepository: this.noteRepository_,
eventBus: this.eventBus_,
})
cloned.transactionManager_ = transactionManager
return cloned
}
/**
* Retrieves a specific note.
* @param {*} id - the id of the note to retrieve.
* @param {*} config - any options needed to query for the result.
* @returns {Promise} which resolves to the requested note.
*/
async retrieve(id, config = {}) {
const noteRepo = this.manager_.getCustomRepository(this.noteRepository_)
const validatedId = this.validateId_(id)
const query = this.buildQuery_({ id: validatedId }, config)
const note = await noteRepo.findOne(query)
if (!note) {
throw new MedusaError(
MedusaError.Types.NOT_FOUND,
`Note with id: ${id} was not found.`
)
}
return note
}
/** Fetches all notes related to the given selector
* @param {Object} selector - the query object for find
* @param {Object} config - the configuration used to find the objects. contains relations, skip, and take.
* @return {Promise<Note[]>} notes related to the given search.
*/
async list(
selector,
config = {
skip: 0,
take: 50,
relations: [],
}
) {
const noteRepo = this.manager_.getCustomRepository(this.noteRepository_)
const query = this.buildQuery_(selector, config)
return noteRepo.find(query)
}
/**
* Creates a note associated with a given author
* @param {object} data - the note to create
* @param {*} config - any configurations if needed, including meta data
* @returns {Promise} resolves to the creation result
*/
async create(data, config = { metadata: {} }) {
const { metadata } = config
const { resource_id, resource_type, value, author_id } = data
return this.atomicPhase_(async manager => {
const noteRepo = manager.getCustomRepository(this.noteRepository_)
const toCreate = {
resource_id,
resource_type,
value,
author_id,
metadata,
}
const note = await noteRepo.create(toCreate)
const result = await noteRepo.save(note)
await this.eventBus_
.withTransaction(manager)
.emit(NoteService.Events.CREATED, { id: result.id })
return result
})
}
/**
* Updates a given note with a new value
* @param {*} noteId - the id of the note to update
* @param {*} value - the new value
* @returns {Promise} resolves to the updated element
*/
async update(noteId, value) {
return this.atomicPhase_(async manager => {
const noteRepo = manager.getCustomRepository(this.noteRepository_)
const note = await this.retrieve(noteId, { relations: ["author"] })
note.value = value
const result = await noteRepo.save(note)
await this.eventBus_
.withTransaction(manager)
.emit(NoteService.Events.UPDATED, { id: result.id })
return result
})
}
/**
* Deletes a given note
* @param {*} noteId - id of the note to delete
* @returns {Promise}
*/
async delete(noteId) {
return this.atomicPhase_(async manager => {
const noteRepo = manager.getCustomRepository(this.noteRepository_)
const note = await this.retrieve(noteId)
await noteRepo.softRemove(note)
await this.eventBus_
.withTransaction(manager)
.emit(NoteService.Events.DELETED, { id: noteId })
return Promise.resolve()
})
}
}
export default NoteService
+1 -1
View File
@@ -41,7 +41,7 @@ class NotificationService extends BaseService {
/**
* Sets the service's manager to a given transaction manager.
* @parma {EntityManager} transactionManager - the manager to use
* @param {EntityManager} transactionManager - the manager to use
* return {NotificationService} a cloned notification service
*/
withTransaction(transactionManager) {
+11 -3
View File
@@ -1,5 +1,4 @@
import _ from "lodash"
import { Validator, MedusaError } from "medusa-core-utils"
import { MedusaError, Validator } from "medusa-core-utils"
import { BaseService } from "medusa-interfaces"
import { Brackets } from "typeorm"
@@ -237,7 +236,9 @@ class OrderService extends BaseService {
qb.andWhere(
new Brackets(qb => {
qb.where(`shipping_address.first_name ILIKE :q`, { q: `%${q}%` })
qb.where(`shipping_address.first_name ILIKE :qfn`, {
qfn: `%${q}%`,
})
.orWhere(`order.email ILIKE :q`, { q: `%${q}%` })
.orWhere(`display_id::varchar(255) ILIKE :dId`, { dId: `${q}` })
})
@@ -477,6 +478,9 @@ class OrderService extends BaseService {
.withTransaction(manager)
.cancelPayment(payment)
}
await this.cartService_
.withTransaction(manager)
.update(cart.id, { payment_authorized_at: null })
throw err
}
}
@@ -595,6 +599,10 @@ class OrderService extends BaseService {
no_notification: result.no_notification,
})
await this.cartService_
.withTransaction(manager)
.update(cart.id, { completed_at: new Date() })
return result
})
}
@@ -328,7 +328,7 @@ class PaymentProviderService extends BaseService {
payment.canceled_at = now.toISOString()
const paymentRepo = manager.getCustomRepository(this.paymentRepository_)
return paymentRepo.save(payment)
return await paymentRepo.save(payment)
})
}
@@ -1,6 +1,6 @@
import _ from "lodash"
import { BaseService } from "medusa-interfaces"
import { Brackets, Raw, IsNull } from "typeorm"
import { Brackets, Raw, IsNull, ILike } from "typeorm"
import { Validator, MedusaError } from "medusa-core-utils"
/**
@@ -558,14 +558,11 @@ class ProductVariantService extends BaseService {
}
query.where = qb => {
qb.where(where).andWhere(
new Brackets(qb => {
qb.where([
{ sku: Raw(a => `${a} ILIKE :q`, { q: `%${q}%` }) },
{ title: Raw(a => `${a} ILIKE :q`, { q: `%${q}%` }) },
]).orWhere(`product.title ILIKE :q`, { q: `%${q}%` })
})
)
qb.where(where).andWhere([
{ sku: ILike(`%${q}%`) },
{ title: ILike(`%${q}%`) },
{ product: { title: ILike(`%${q}%`) } },
])
}
}
+39 -3
View File
@@ -1,6 +1,6 @@
import _ from "lodash"
import { Validator, MedusaError } from "medusa-core-utils"
import { BaseService } from "medusa-interfaces"
import { In } from "typeorm"
class ReturnReasonService extends BaseService {
constructor({ manager, returnReasonRepository }) {
@@ -32,6 +32,17 @@ class ReturnReasonService extends BaseService {
return this.atomicPhase_(async manager => {
const rrRepo = manager.getCustomRepository(this.retReasonRepo_)
if (data.parent_return_reason_id && data.parent_return_reason_id !== "") {
const parentReason = await this.retrieve(data.parent_return_reason_id)
if (parentReason.parent_return_reason_id) {
throw new MedusaError(
MedusaError.Types.INVALID_DATA,
"Doubly nested return reasons is not supported"
)
}
}
const created = rrRepo.create(data)
const result = await rrRepo.save(created)
@@ -44,14 +55,20 @@ class ReturnReasonService extends BaseService {
const rrRepo = manager.getCustomRepository(this.retReasonRepo_)
const reason = await this.retrieve(id)
if ("description" in data) {
const { description, label, parent_return_reason_id } = data
if (description) {
reason.description = data.description
}
if ("label" in data) {
if (label) {
reason.label = data.label
}
if (parent_return_reason_id) {
reason.parent_return_reason_id = parent_return_reason_id
}
await rrRepo.save(reason)
return reason
@@ -92,6 +109,25 @@ class ReturnReasonService extends BaseService {
return item
}
async delete(returnReasonId) {
return this.atomicPhase_(async manager => {
const rrRepo = manager.getCustomRepository(this.retReasonRepo_)
// We include the relation 'return_reason_children' to enable cascading deletes of return reasons if a parent is removed
const reason = await this.retrieve(returnReasonId, {
relations: ["return_reason_children"],
})
if (!reason) {
return Promise.resolve()
}
await rrRepo.softRemove(reason)
return Promise.resolve()
})
}
}
export default ReturnReasonService
+12
View File
@@ -374,6 +374,18 @@ class ReturnService extends BaseService {
refund_amount: Math.floor(toRefund),
}
const returnReasons = await this.returnReasonService_.list(
{ id: [...returnLines.map(rl => rl.reason_id)] },
{ relations: ["return_reason_children"] }
)
if (returnReasons.some(rr => rr.return_reason_children?.length > 0)) {
throw new MedusaError(
MedusaError.Types.INVALID_DATA,
"Cannot apply return reason category"
)
}
const rItemRepo = manager.getCustomRepository(this.returnItemRepository_)
returnObject.items = returnLines.map(i =>
rItemRepo.create({
@@ -96,6 +96,13 @@ class ShippingOptionService extends BaseService {
throw new MedusaError(MedusaError.Types.INVALID_DATA, "ID does not exist")
}
// If no option id is provided, we are currently in the process of creating
// a new shipping option. Therefore, simply return the requirement, such
// that the cascading will take care of the creation of the requirement.
if (!optionId) {
return requirement
}
let req
if (existingReq) {
req = await reqRepo.save({
@@ -359,11 +366,33 @@ class ShippingOptionService extends BaseService {
}
if ("requirements" in data) {
option.requirements = await Promise.all(
data.requirements.map(r => {
return this.validateRequirement_(r, option.id)
})
)
const acc = []
for (const r of data.requirements) {
const validated = await this.validateRequirement_(r)
if (acc.find(raw => raw.type === validated.type)) {
throw new MedusaError(
MedusaError.Types.INVALID_DATA,
"Only one requirement of each type is allowed"
)
}
if (
acc.find(
raw =>
(raw.type === "max_subtotal" &&
validated.amount > raw.amount) ||
(raw.type === "min_subtotal" && validated.amount < raw.amount)
)
) {
throw new MedusaError(
MedusaError.Types.INVALID_DATA,
"Max. subtotal must be greater than Min. subtotal"
)
}
acc.push(validated)
}
}
const result = await optionRepo.save(option)
+24 -7
View File
@@ -672,20 +672,33 @@ class SwapService extends BaseService {
}
const cart = swap.cart
const { payment } = cart
const items = swap.cart.items
for (const item of items) {
await this.inventoryService_
.withTransaction(manager)
.confirmInventory(item.variant_id, item.quantity)
if (!swap.allow_backorder) {
for (const item of items) {
try {
await this.inventoryService_
.withTransaction(manager)
.confirmInventory(item.variant_id, item.quantity)
} catch (err) {
if (payment) {
await this.paymentProviderService_
.withTransaction(manager)
.cancelPayment(payment)
}
await this.cartService_
.withTransaction(manager)
.update(cart.id, { payment_authorized_at: null })
throw err
}
}
}
const total = await this.totalsService_.getTotal(cart)
if (total > 0) {
const { payment } = cart
if (!payment) {
throw new MedusaError(
MedusaError.Types.INVALID_ARGUMENT,
@@ -724,7 +737,7 @@ class SwapService extends BaseService {
swap.shipping_address_id = cart.shipping_address_id
swap.shipping_methods = cart.shipping_methods
swap.confirmed_at = now.toISOString()
swap.payment_status = total === 0 ? "difference_refunded" : "awaiting"
swap.payment_status = total === 0 ? "confirmed" : "awaiting"
const swapRepo = manager.getCustomRepository(this.swapRepository_)
const result = await swapRepo.save(swap)
@@ -744,6 +757,10 @@ class SwapService extends BaseService {
no_notification: swap.no_notification,
})
await this.cartService_
.withTransaction(manager)
.update(cart.id, { completed_at: new Date() })
return result
})
}
+15 -6
View File
@@ -48,7 +48,18 @@ class UserService extends BaseService {
* @return {string} the validated email
*/
validateEmail_(email) {
return email
const schema = Validator.string()
.email()
.required()
const { value, error } = schema.validate(email)
if (error) {
throw new MedusaError(
MedusaError.Types.INVALID_DATA,
"The email is not valid"
)
}
return value.toLowerCase()
}
/**
@@ -114,13 +125,11 @@ class UserService extends BaseService {
* @param {string} email - the email of the user to get.
* @return {Promise<User>} the user document.
*/
async retrieveByEmail(email, relations = []) {
async retrieveByEmail(email, config = {}) {
const userRepo = this.manager_.getCustomRepository(this.userRepository_)
const user = await userRepo.findOne({
where: { email },
relations,
})
const query = this.buildQuery_({ email: email.toLowerCase() }, config)
const user = await userRepo.findOne(query)
if (!user) {
throw new MedusaError(
+39 -31
View File
@@ -1407,10 +1407,10 @@
"@types/yargs" "^15.0.0"
chalk "^3.0.0"
"@medusajs/medusa-cli@1.1.16-dev-1631019393655":
version "1.1.16-dev-1631019393655"
resolved "http://localhost:4873/@medusajs%2fmedusa-cli/-/medusa-cli-1.1.16-dev-1631019393655.tgz#68fc30f7053df428cd7a4e5a443326371818a20d"
integrity sha512-5wUVfTJahuHBbY4U5iuwSKbE/Dn3S/fC8MWVpP5R/JFNK484zR3On1qBL0I9tXtsUd9CmB+jKI59w6D49wJ29A==
"@medusajs/medusa-cli@^1.1.18":
version "1.1.18"
resolved "https://registry.yarnpkg.com/@medusajs/medusa-cli/-/medusa-cli-1.1.18.tgz#a2b34575a81a7df239d6d06cf0d0b192e2b8c8db"
integrity sha512-JEvQVjebaGuOF5BsqjZYnewmU4TPbrnhODKVyadPKPb/cxPcCMODg21d5QyoaVlcXood08LgTFe8CfdWoyubVw==
dependencies:
"@babel/polyfill" "^7.8.7"
"@babel/runtime" "^7.9.6"
@@ -1428,8 +1428,8 @@
is-valid-path "^0.1.1"
joi-objectid "^3.0.1"
meant "^1.0.1"
medusa-core-utils "1.1.20-dev-1631019393655"
medusa-telemetry "0.0.3-dev-1631019393655"
medusa-core-utils "^0.1.27"
medusa-telemetry "^0.0.5"
netrc-parser "^3.1.6"
open "^8.0.6"
ora "^5.4.1"
@@ -2046,10 +2046,10 @@ babel-preset-jest@^25.5.0:
babel-plugin-jest-hoist "^25.5.0"
babel-preset-current-node-syntax "^0.1.2"
babel-preset-medusa-package@1.1.13-dev-1631019393655:
version "1.1.13-dev-1631019393655"
resolved "http://localhost:4873/babel-preset-medusa-package/-/babel-preset-medusa-package-1.1.13-dev-1631019393655.tgz#95b20e00ae34b6b1d5a3be2dbd17c4bf45c1895e"
integrity sha512-cpaVSi2+M8LFlZjfcazmp1GHB+lEgDNZNlX8J9RdB5/LWZwWRrqFGVyv//8hXp7AKiFFTtayJmpgBAEFfLhdcA==
babel-preset-medusa-package@^1.1.15:
version "1.1.15"
resolved "https://registry.yarnpkg.com/babel-preset-medusa-package/-/babel-preset-medusa-package-1.1.15.tgz#6917cadd8abe9a1f64c71b5c43ab507df193effc"
integrity sha512-toA8mFdvLeKbbRJ7KvQvpL6VJnzkKURZv7Yd97cXMMNpdjrhp+SZppcNOL2tk6ywgBAs4NC2LCVjtZInMMBS6Q==
dependencies:
"@babel/plugin-proposal-class-properties" "^7.12.1"
"@babel/plugin-proposal-decorators" "^7.12.1"
@@ -5454,25 +5454,33 @@ media-typer@0.3.0:
resolved "https://registry.yarnpkg.com/media-typer/-/media-typer-0.3.0.tgz#8710d7af0aa626f8fffa1ce00168545263255748"
integrity sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=
medusa-core-utils@1.1.20-dev-1631019393655:
version "1.1.20-dev-1631019393655"
resolved "http://localhost:4873/medusa-core-utils/-/medusa-core-utils-1.1.20-dev-1631019393655.tgz#edd5e25518677799647eef32ccc8ddb4f244026e"
integrity sha512-Ko+gjpe4pHwQMI6Gv4MxMlzWbQbCL9bnN8MX5eHwLJoGcPzt57y5/RWujJo99nUuNthUDiJ14EFXQHVQu45bmQ==
medusa-core-utils@^0.1.27:
version "0.1.39"
resolved "https://registry.yarnpkg.com/medusa-core-utils/-/medusa-core-utils-0.1.39.tgz#d57816c9bd43f9a92883650c1e66add1665291df"
integrity sha512-R8+U1ile7if+nR6Cjh5exunx0ETV0OfkWUUBUpz1KmHSDv0V0CcvQqU9lcZesPFDEbu3Y2iEjsCqidVA4nG2nQ==
dependencies:
"@hapi/joi" "^16.1.8"
joi-objectid "^3.0.1"
medusa-core-utils@^1.1.22:
version "1.1.22"
resolved "https://registry.yarnpkg.com/medusa-core-utils/-/medusa-core-utils-1.1.22.tgz#84ce0af0a7c672191d758ea462056e30a39d08b1"
integrity sha512-kMuRkWOuNG4Bw6epg/AYu95UJuE+rjHTeTWRLbEPrYGjWREV82tLWVDI21/QcccmaHmMU98Rkw2z9JwyFZIiyw==
dependencies:
joi "^17.3.0"
joi-objectid "^3.0.1"
medusa-interfaces@1.1.21-dev-1631019393655:
version "1.1.21-dev-1631019393655"
resolved "http://localhost:4873/medusa-interfaces/-/medusa-interfaces-1.1.21-dev-1631019393655.tgz#c2a72b6751a802438ed0f9010ed0cfecff1d1a2d"
integrity sha512-I8BEdvmVKorakTU5myw8IyTLp9OcO40OkNdZmZj29Q4oRU4scwP3i+5bXd6xW0ML5t5hbMUT9TKU9n7oZNIvug==
medusa-interfaces@^1.1.23:
version "1.1.23"
resolved "https://registry.yarnpkg.com/medusa-interfaces/-/medusa-interfaces-1.1.23.tgz#b552a8c1d0eaddeff30472ab238652b9e1a56e73"
integrity sha512-dHCOnsyYQvjrtRd3p0ZqQZ4M/zmo4M/BAgVfRrYSyGrMdQ86TK9Z1DQDCHEzM1216AxEfXz2JYUD7ilTfG2iHQ==
dependencies:
medusa-core-utils "1.1.20-dev-1631019393655"
medusa-core-utils "^1.1.22"
medusa-telemetry@0.0.3-dev-1631019393655:
version "0.0.3-dev-1631019393655"
resolved "http://localhost:4873/medusa-telemetry/-/medusa-telemetry-0.0.3-dev-1631019393655.tgz#dc6abef74c631520d4d8c81a4175ac795d72fbda"
integrity sha512-I8a8iTTmL0u5/SYnTw02xn3JiFa2PDclRyDL5pAl8gFgCzVCWD+fm+ik51kMMhbNhK7GWobs4h5xxA4W6eCcAQ==
medusa-telemetry@^0.0.5:
version "0.0.5"
resolved "https://registry.yarnpkg.com/medusa-telemetry/-/medusa-telemetry-0.0.5.tgz#d7d08fca5cbecc0e853b4e0406194a92c5206caa"
integrity sha512-h7hP5Lc33OkFhMcvfrPcwINzMOuPoG8Vn8O6niKGFxF9RmmQnJgaAG1J43/Eq9ZWBrWi0n42XBttibKwCMViHw==
dependencies:
axios "^0.21.1"
axios-retry "^3.1.9"
@@ -5484,13 +5492,13 @@ medusa-telemetry@0.0.3-dev-1631019393655:
remove-trailing-slash "^0.1.1"
uuid "^8.3.2"
medusa-test-utils@1.1.23-dev-1631019393655:
version "1.1.23-dev-1631019393655"
resolved "http://localhost:4873/medusa-test-utils/-/medusa-test-utils-1.1.23-dev-1631019393655.tgz#1daf3913cfe8a9586863e4b80dabbb7344ebba48"
integrity sha512-5qsKVr2whQi03lGPmy2+/tAD1KuFsqrAcaaEp+u42Jjg6acDV1rrcTgFLj+lplRBOC2qFzMEU0cBFuGKNmYhpg==
medusa-test-utils@^1.1.25:
version "1.1.25"
resolved "https://registry.yarnpkg.com/medusa-test-utils/-/medusa-test-utils-1.1.25.tgz#7c4aa8a70ec8a95875304258ffbe7493a1e5a7fc"
integrity sha512-4xy20KsZBR1XcuzckGRq9A+GJwh+CFHzVw3dajaO4iiNpL/a9K3Yj2N4f/8BgRcQyw5PnkKGJ0pzv+OR8+5GVw==
dependencies:
"@babel/plugin-transform-classes" "^7.9.5"
medusa-core-utils "1.1.20-dev-1631019393655"
medusa-core-utils "^1.1.22"
randomatic "^3.1.1"
merge-descriptors@1.0.1:
@@ -7639,9 +7647,9 @@ tmp@^0.0.33:
os-tmpdir "~1.0.2"
tmpl@1.0.x:
version "1.0.4"
resolved "https://registry.yarnpkg.com/tmpl/-/tmpl-1.0.4.tgz#23640dd7b42d00433911140820e5cf440e521dd1"
integrity sha1-I2QN17QtAEM5ERQIIOXPRA5SHdE=
version "1.0.5"
resolved "https://registry.yarnpkg.com/tmpl/-/tmpl-1.0.5.tgz#8683e0b902bb9c20c4f726e3c0b69f36518c07cc"
integrity sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==
to-fast-properties@^2.0.0:
version "2.0.0"