fix: merge conflicts
This commit is contained in:
commit
632ad17e3f
134 files changed
+5517
-628
No files matched your search
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"plugins": [
|
||||
"@babel/plugin-proposal-class-properties",
|
||||
"@babel/plugin-transform-instanceof",
|
||||
"@babel/plugin-transform-classes"
|
||||
],
|
||||
"presets": ["@babel/preset-env"],
|
||||
"env": {
|
||||
"test": {
|
||||
"plugins": ["@babel/plugin-transform-runtime"]
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"plugins": ["prettier"],
|
||||
"extends": ["prettier"],
|
||||
"rules": {
|
||||
"prettier/prettier": "error",
|
||||
"semi": "error",
|
||||
"no-unused-expressions": "true"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
/lib
|
||||
node_modules
|
||||
.DS_store
|
||||
.env*
|
||||
/*.js
|
||||
!index.js
|
||||
yarn.lock
|
||||
|
||||
/dist
|
||||
|
||||
/api
|
||||
/services
|
||||
/models
|
||||
/subscribers
|
||||
/__mocks__
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
/lib
|
||||
node_modules
|
||||
.DS_store
|
||||
.env*
|
||||
/*.js
|
||||
!index.js
|
||||
yarn.lock
|
||||
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"endOfLine": "lf",
|
||||
"semi": false,
|
||||
"singleQuote": false,
|
||||
"tabWidth": 2,
|
||||
"trailingComma": "es5"
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
// noop
|
||||
@@ -0,0 +1,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,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
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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"`)
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -46,7 +46,7 @@ export class Customer {
|
||||
)
|
||||
shipping_addresses: Address[]
|
||||
|
||||
@Column({ nullable: true })
|
||||
@Column({ nullable: true, select: false })
|
||||
password_hash: string
|
||||
|
||||
@Column({ nullable: true })
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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
|
||||
*/
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 })
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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)
|
||||
})
|
||||
|
||||
@@ -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}%`) })
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
@@ -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,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
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
|
||||
@@ -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) {
|
||||
|
||||
@@ -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}%`) } },
|
||||
])
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
})
|
||||
}
|
||||
|
||||
@@ -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
@@ -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"
|
||||
|
||||
Reference in new issue
Block a user