From 8bfaf1516b3fdcb44abe5610a2cf4e825716575b Mon Sep 17 00:00:00 2001 From: Sujith Date: Mon, 17 Oct 2022 23:35:48 +0530 Subject: [PATCH 01/33] feat: initial commit fulfillment base Signed-off-by: Sujith --- packages/api-plugin-fulfillment/.gitignore | 61 +++ packages/api-plugin-fulfillment/LICENSE | 201 +++++++++ packages/api-plugin-fulfillment/README.md | 68 +++ .../api-plugin-fulfillment/babel.config.cjs | 1 + packages/api-plugin-fulfillment/index.js | 3 + .../api-plugin-fulfillment/jest.config.cjs | 1 + packages/api-plugin-fulfillment/package.json | 49 +++ packages/api-plugin-fulfillment/src/index.js | 59 +++ .../createFulfillmentMethod.test.js.snap | 3 + .../src/mutations/createFulfillmentMethod.js | 59 +++ .../mutations/createFulfillmentMethod.test.js | 72 +++ .../src/mutations/createFulfillmentType.js | 38 ++ .../src/mutations/index.js | 15 + .../selectFulfillmentOptionForGroup.js | 57 +++ .../src/mutations/updateFulfillmentMethod.js | 69 +++ .../updateFulfillmentOptionsForGroup.js | 102 +++++ .../src/mutations/updateFulfillmentType.js | 55 +++ .../api-plugin-fulfillment/src/preStartup.js | 21 + .../src/queries/getFulfillmentMethods.js | 47 ++ .../getFulfillmentMethodsWithQuotes.js | 50 +++ .../src/queries/getFulfillmentType.js | 29 ++ .../src/queries/getFulfillmentTypes.js | 21 + .../src/queries/index.js | 12 + .../src/registration.js | 27 ++ .../src/resolvers/FulfillmentMethod/index.js | 5 + .../src/resolvers/Mutation/index.js | 11 + .../selectFulfillmentOptionForGroup.js | 43 ++ .../Mutation/updateFulfillmentMethod.js | 35 ++ .../updateFulfillmentOptionsForGroup.js | 32 ++ .../Mutation/updateFulfillmentType.js | 34 ++ .../resolvers/Query/getFulfillmentMethods.js | 32 ++ .../src/resolvers/Query/getFulfillmentType.js | 28 ++ .../resolvers/Query/getFulfillmentTypes.js | 31 ++ .../src/resolvers/Query/index.js | 9 + .../src/resolvers/index.js | 19 + .../src/schemas/index.js | 5 + .../src/schemas/schema.graphql | 414 ++++++++++++++++++ .../src/simpleSchemas.js | 133 ++++++ .../api-plugin-fulfillment/src/startup.js | 30 ++ .../src/tests/factory.js | 20 + .../src/util/extendCommonOrder.js | 93 ++++ .../src/util/getCartById.js | 34 ++ .../api-plugin-fulfillment/src/xforms/id.js | 18 + pnpm-lock.yaml | 230 ++++++++-- 44 files changed, 2335 insertions(+), 41 deletions(-) create mode 100644 packages/api-plugin-fulfillment/.gitignore create mode 100644 packages/api-plugin-fulfillment/LICENSE create mode 100644 packages/api-plugin-fulfillment/README.md create mode 100644 packages/api-plugin-fulfillment/babel.config.cjs create mode 100644 packages/api-plugin-fulfillment/index.js create mode 100644 packages/api-plugin-fulfillment/jest.config.cjs create mode 100644 packages/api-plugin-fulfillment/package.json create mode 100644 packages/api-plugin-fulfillment/src/index.js create mode 100644 packages/api-plugin-fulfillment/src/mutations/__snapshots__/createFulfillmentMethod.test.js.snap create mode 100644 packages/api-plugin-fulfillment/src/mutations/createFulfillmentMethod.js create mode 100644 packages/api-plugin-fulfillment/src/mutations/createFulfillmentMethod.test.js create mode 100644 packages/api-plugin-fulfillment/src/mutations/createFulfillmentType.js create mode 100644 packages/api-plugin-fulfillment/src/mutations/index.js create mode 100644 packages/api-plugin-fulfillment/src/mutations/selectFulfillmentOptionForGroup.js create mode 100644 packages/api-plugin-fulfillment/src/mutations/updateFulfillmentMethod.js create mode 100644 packages/api-plugin-fulfillment/src/mutations/updateFulfillmentOptionsForGroup.js create mode 100644 packages/api-plugin-fulfillment/src/mutations/updateFulfillmentType.js create mode 100644 packages/api-plugin-fulfillment/src/preStartup.js create mode 100644 packages/api-plugin-fulfillment/src/queries/getFulfillmentMethods.js create mode 100644 packages/api-plugin-fulfillment/src/queries/getFulfillmentMethodsWithQuotes.js create mode 100644 packages/api-plugin-fulfillment/src/queries/getFulfillmentType.js create mode 100644 packages/api-plugin-fulfillment/src/queries/getFulfillmentTypes.js create mode 100644 packages/api-plugin-fulfillment/src/queries/index.js create mode 100644 packages/api-plugin-fulfillment/src/registration.js create mode 100644 packages/api-plugin-fulfillment/src/resolvers/FulfillmentMethod/index.js create mode 100644 packages/api-plugin-fulfillment/src/resolvers/Mutation/index.js create mode 100644 packages/api-plugin-fulfillment/src/resolvers/Mutation/selectFulfillmentOptionForGroup.js create mode 100644 packages/api-plugin-fulfillment/src/resolvers/Mutation/updateFulfillmentMethod.js create mode 100644 packages/api-plugin-fulfillment/src/resolvers/Mutation/updateFulfillmentOptionsForGroup.js create mode 100644 packages/api-plugin-fulfillment/src/resolvers/Mutation/updateFulfillmentType.js create mode 100644 packages/api-plugin-fulfillment/src/resolvers/Query/getFulfillmentMethods.js create mode 100644 packages/api-plugin-fulfillment/src/resolvers/Query/getFulfillmentType.js create mode 100644 packages/api-plugin-fulfillment/src/resolvers/Query/getFulfillmentTypes.js create mode 100644 packages/api-plugin-fulfillment/src/resolvers/Query/index.js create mode 100644 packages/api-plugin-fulfillment/src/resolvers/index.js create mode 100644 packages/api-plugin-fulfillment/src/schemas/index.js create mode 100644 packages/api-plugin-fulfillment/src/schemas/schema.graphql create mode 100644 packages/api-plugin-fulfillment/src/simpleSchemas.js create mode 100644 packages/api-plugin-fulfillment/src/startup.js create mode 100644 packages/api-plugin-fulfillment/src/tests/factory.js create mode 100644 packages/api-plugin-fulfillment/src/util/extendCommonOrder.js create mode 100644 packages/api-plugin-fulfillment/src/util/getCartById.js create mode 100644 packages/api-plugin-fulfillment/src/xforms/id.js diff --git a/packages/api-plugin-fulfillment/.gitignore b/packages/api-plugin-fulfillment/.gitignore new file mode 100644 index 00000000000..ad46b30886f --- /dev/null +++ b/packages/api-plugin-fulfillment/.gitignore @@ -0,0 +1,61 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* + +# Runtime data +pids +*.pid +*.seed +*.pid.lock + +# Directory for instrumented libs generated by jscoverage/JSCover +lib-cov + +# Coverage directory used by tools like istanbul +coverage + +# nyc test coverage +.nyc_output + +# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) +.grunt + +# Bower dependency directory (https://bower.io/) +bower_components + +# node-waf configuration +.lock-wscript + +# Compiled binary addons (https://nodejs.org/api/addons.html) +build/Release + +# Dependency directories +node_modules/ +jspm_packages/ + +# TypeScript v1 declaration files +typings/ + +# Optional npm cache directory +.npm + +# Optional eslint cache +.eslintcache + +# Optional REPL history +.node_repl_history + +# Output of 'npm pack' +*.tgz + +# Yarn Integrity file +.yarn-integrity + +# dotenv environment variables file +.env + +# next.js build output +.next diff --git a/packages/api-plugin-fulfillment/LICENSE b/packages/api-plugin-fulfillment/LICENSE new file mode 100644 index 00000000000..261eeb9e9f8 --- /dev/null +++ b/packages/api-plugin-fulfillment/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/packages/api-plugin-fulfillment/README.md b/packages/api-plugin-fulfillment/README.md new file mode 100644 index 00000000000..53aaf9acead --- /dev/null +++ b/packages/api-plugin-fulfillment/README.md @@ -0,0 +1,68 @@ +# api-plugin-fulfillment + +[![npm (scoped)](https://img.shields.io/npm/v/@reactioncommerce/api-plugin-fulfillment.svg)](https://www.npmjs.com/package/@reactioncommerce/api-plugin-fulfillment) +[![CircleCI](https://circleci.com/gh/reactioncommerce/api-plugin-fulfillment.svg?style=svg)](https://circleci.com/gh/reactioncommerce/api-plugin-fulfillment) + + +## Summary + +This plugin provides the base for all fulfillment types and the fulfillment methods under each type. + +## Included in this fulfillment plugin +### `src/` + +The `src` folder is where all the plugin files resides. + +### `.gitignore` + +A basic `gitignore` file +### `babel.config.cjs` + +If your plugin includes linting and tests, this file is required to allow esmodules to run correctly. + +### `jest.config.cjs` + +If your plugin includes tests, this file is required to allow esmodules to run correctly. You'll need to update the `transformIgnorePatterns` and `moduleNameMapper` sections to include any esmodule `npm` packages used in your plugin. + +### `License.md` + +If your plugin uses `Apache 2` licensing, you can leave this file as-is. If another type of licensing is used, you need to update this file, and the README, accordingly. + +### `package.json` + +The provided `package.json` is set up to install all needed packages and config for linting, testing, and semantic-release. You'll need to update the `name`, `description`, and add any new dependencies your plugin files use. + +### `index.js` + +The entrypoint file for your npm package, will most likely just export your plugin registration from the `src` folder. + +## Developer Certificate of Origin +We use the [Developer Certificate of Origin (DCO)](https://developercertificate.org/) in lieu of a Contributor License Agreement for all contributions to Reaction Commerce open source projects. We request that contributors agree to the terms of the DCO and indicate that agreement by signing all commits made to Reaction Commerce projects by adding a line with your name and email address to every Git commit message contributed: +``` +Signed-off-by: Jane Doe +``` + +You can sign your commit automatically with Git by using `git commit -s` if you have your `user.name` and `user.email` set as part of your Git configuration. + +We ask that you use your real name (please no anonymous contributions or pseudonyms). By signing your commit you are certifying that you have the right have the right to submit it under the open source license used by that particular Reaction Commerce project. You must use your real name (no pseudonyms or anonymous contributions are allowed.) + +We use the [Probot DCO GitHub app](https://github.com/apps/dco) to check for DCO signoffs of every commit. + +If you forget to sign your commits, the DCO bot will remind you and give you detailed instructions for how to amend your commits to add a signature. + +## License + + Copyright 2020 Reaction Commerce + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + diff --git a/packages/api-plugin-fulfillment/babel.config.cjs b/packages/api-plugin-fulfillment/babel.config.cjs new file mode 100644 index 00000000000..5fa924c0809 --- /dev/null +++ b/packages/api-plugin-fulfillment/babel.config.cjs @@ -0,0 +1 @@ +module.exports = require("@reactioncommerce/api-utils/lib/configs/babel.config.cjs"); diff --git a/packages/api-plugin-fulfillment/index.js b/packages/api-plugin-fulfillment/index.js new file mode 100644 index 00000000000..d7ea8b28c59 --- /dev/null +++ b/packages/api-plugin-fulfillment/index.js @@ -0,0 +1,3 @@ +import register from "./src/index.js"; + +export default register; diff --git a/packages/api-plugin-fulfillment/jest.config.cjs b/packages/api-plugin-fulfillment/jest.config.cjs new file mode 100644 index 00000000000..2bdefefceb9 --- /dev/null +++ b/packages/api-plugin-fulfillment/jest.config.cjs @@ -0,0 +1 @@ +module.exports = require("@reactioncommerce/api-utils/lib/configs/jest.config.cjs"); diff --git a/packages/api-plugin-fulfillment/package.json b/packages/api-plugin-fulfillment/package.json new file mode 100644 index 00000000000..5497612a419 --- /dev/null +++ b/packages/api-plugin-fulfillment/package.json @@ -0,0 +1,49 @@ +{ + "name": "@reactioncommerce/api-plugin-fulfillment", + "description": "Base Fulfillment plugin for implementing other fulfillment type plugins and their corresponding fulfillment method plugins", + "label": "Fulfillment Plugin", + "version": "1.0.0", + "main": "index.js", + "type": "module", + "engines": { + "node": ">=14.18.1", + "npm": ">=7" + }, + "homepage": "https://github.com/reactioncommerce/reaction", + "url": "https://github.com/reactioncommerce/reaction.git", + "email": "hello-open-commerce@mailchimp.com", + "repository": { + "type": "git", + "url": "https://github.com/reactioncommerce/reaction.git", + "directory": "packages/api-plugin-fulfillment" + }, + "author": { + "name": "Mailchimp Open Commerce", + "email": "hello-open-commerce@mailchimp.com", + "url": "https://mailchimp.com/developer/open-commerce/" + }, + "license": "Apache-2.0", + "bugs": { + "url": "https://github.com/reactioncommerce/api-plugin-fulfillment/issues" + }, + "sideEffects": false, + "dependencies": { + "@reactioncommerce/logger": "^1.1.3", + "@reactioncommerce/api-utils": "^1.16.9", + "@reactioncommerce/reaction-error": "^1.0.1", + "@reactioncommerce/random": "^1.0.2", + "lodash": "^4.17.21", + "simpl-schema": "^1.12.2" + }, + "devDependencies": { + "@reactioncommerce/api-plugin-carts": "^1.0.0", + "@reactioncommerce/data-factory": "~1.0.1" + }, + "scripts": { + "lint": "npm run lint:eslint", + "lint:eslint": "eslint .", + "test": "jest", + "test:watch": "jest --watch", + "test:file": "jest --no-cache --watch --coverage=false" + } +} diff --git a/packages/api-plugin-fulfillment/src/index.js b/packages/api-plugin-fulfillment/src/index.js new file mode 100644 index 00000000000..2a006b76fac --- /dev/null +++ b/packages/api-plugin-fulfillment/src/index.js @@ -0,0 +1,59 @@ +import { createRequire } from "module"; +import mutations from "./mutations/index.js"; +import queries from "./queries/index.js"; +import resolvers from "./resolvers/index.js"; +import schemas from "./schemas/index.js"; +import startup from "./startup.js"; +import preStartup from "./preStartup.js"; +import { MethodEmptyData } from "./simpleSchemas.js"; +import { allRegisteredFulfillmentTypes, registerPluginHandlerForFulfillmentTypes } from "./registration.js"; + +const require = createRequire(import.meta.url); +const pkg = require("../package.json"); + +/** + * @summary Import and call this function to add this plugin to your API. + * @param {Object} app The ReactionAPI instance + * @returns {undefined} + */ +export default async function register(app) { + await app.registerPlugin({ + label: "Fulfillment", + name: "fulfillment", + version: pkg.version, + collections: { + FulfillmentRestrictions: { + name: "FulfillmentRestrictions", + indexes: [ + [{ methodIds: 1 }] + ] + }, + Fulfillment: { + name: "Fulfillment", + indexes: [ + // Create indexes. We set specific names for backwards compatibility + // with indexes created by the aldeed:schema-index Meteor package. + [{ name: 1 }, { name: "c2_name" }], + [{ shopId: 1 }, { name: "c2_shopId" }] + ] + } + }, + graphQL: { + schemas, + resolvers + }, + queries, + mutations, + functionsByType: { + registerPluginHandler: [registerPluginHandlerForFulfillmentTypes], + preStartup: [preStartup], + startup: [startup] + }, + contextAdditions: { + allRegisteredFulfillmentTypes + }, + simpleSchemas: { + MethodEmptyData + } + }); +} diff --git a/packages/api-plugin-fulfillment/src/mutations/__snapshots__/createFulfillmentMethod.test.js.snap b/packages/api-plugin-fulfillment/src/mutations/__snapshots__/createFulfillmentMethod.test.js.snap new file mode 100644 index 00000000000..79e2bc2c52b --- /dev/null +++ b/packages/api-plugin-fulfillment/src/mutations/__snapshots__/createFulfillmentMethod.test.js.snap @@ -0,0 +1,3 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`throws if required fields are not supplied 1`] = `"Fulfillment types is required"`; diff --git a/packages/api-plugin-fulfillment/src/mutations/createFulfillmentMethod.js b/packages/api-plugin-fulfillment/src/mutations/createFulfillmentMethod.js new file mode 100644 index 00000000000..b0985d03a74 --- /dev/null +++ b/packages/api-plugin-fulfillment/src/mutations/createFulfillmentMethod.js @@ -0,0 +1,59 @@ +import SimpleSchema from "simpl-schema"; +import Random from "@reactioncommerce/random"; +import ReactionError from "@reactioncommerce/reaction-error"; +import { FulfillmentMethodSchema } from "../simpleSchemas.js"; + +const inputSchema = new SimpleSchema({ + method: FulfillmentMethodSchema, + fulfillmentTypeId: String, + shopId: String +}); + +/** + * @method createFulfillmentMethodMutation + * @summary Creates a fulfillment method + * @param {Object} context - an object containing the per-request state + * @param {Object} input - Input (see SimpleSchema) + * @returns {Promise} An object with a `method` property containing the created method + */ +export default async function createFulfillmentMethodMutation(context, input) { + const cleanedInput = inputSchema.clean(input); // add default values and such + inputSchema.validate(cleanedInput); + + const { method: inputMethod, fulfillmentTypeId, shopId } = cleanedInput; + const { collections } = context; + const { Fulfillment } = collections; + const method = { ...inputMethod }; + + await context.validatePermissions("reaction:legacy:fulfillmentMethods", "create", { shopId }); + + const ffTypeRecord = await Fulfillment.findOne({ _id: fulfillmentTypeId, shopId }); + if (!ffTypeRecord) throw new ReactionError("server-error", "Unable to create fulfillment method without defined type"); + + const ffTypeMethodRecord = await Fulfillment.findOne({ + _id: fulfillmentTypeId, + shopId, + methods: { $elemMatch: { name: method.name, fulfillmentMethod: method.fulfillmentMethod } } + }); + if (ffTypeMethodRecord) throw new ReactionError("server-error", "Fulfillment Method already exists"); + + method._id = Random.id(); + // MongoDB schema still uses `enabled` rather than `isEnabled` + // method.enabled = method.isEnabled; + // delete method.isEnabled; + + method.fulfillmentTypes = [ffTypeRecord.fulfillmentType]; + + const { matchedCount } = await Fulfillment.updateOne({ + shopId, + _id: fulfillmentTypeId + }, { + $addToSet: { + methods: method + } + }); + + if (matchedCount === 0) throw new ReactionError("server-error", "Unable to create fulfillment method"); + + return { group: method }; +} diff --git a/packages/api-plugin-fulfillment/src/mutations/createFulfillmentMethod.test.js b/packages/api-plugin-fulfillment/src/mutations/createFulfillmentMethod.test.js new file mode 100644 index 00000000000..9248b986a28 --- /dev/null +++ b/packages/api-plugin-fulfillment/src/mutations/createFulfillmentMethod.test.js @@ -0,0 +1,72 @@ +import mockCollection from "@reactioncommerce/api-utils/tests/mockCollection.js"; +import mockContext from "@reactioncommerce/api-utils/tests/mockContext.js"; +import createFulfillmentMethodMutation from "./createFulfillmentMethod.js"; + +mockContext.validatePermissions = jest.fn().mockName("validatePermissions"); +mockContext.collections.Fulfillment = mockCollection("Fulfillment"); +mockContext.validatePermissions.mockReturnValueOnce(Promise.resolve(null)); + +test("throws if required fields are not supplied", async () => { + const fulfillmentMethodInput = { + method: { + shopId: "SHOP_ID", + cost: 99, + fulfillmentMethod: "flatRates", + displayMessageMethod: "Sample display message" + }, + fulfillmentTypeId: "fulfillment123", + shopId: "SHOP_ID" + }; + + await expect(createFulfillmentMethodMutation(mockContext, fulfillmentMethodInput)).rejects.toThrowErrorMatchingSnapshot(); +}); + +test("add a new fulfillment method", async () => { + mockContext.collections.Fulfillment.updateOne.mockReturnValueOnce(Promise.resolve({ result: { ok: 1 } })); + mockContext.collections.Fulfillment.findOne.mockReturnValueOnce(Promise.resolve({ + _id: "fulfillment123", + name: "Default Shipping Provider", + shopId: "SHOP_ID", + provider: { + enabled: true, + label: "Shipping", + name: "shipping" + }, + fulfillmentType: "shipping" + })); + + + const result = await createFulfillmentMethodMutation(mockContext, { + method: { + shopId: "SHOP_ID", + cost: 99, + handling: 99, + rate: 99, + fulfillmentTypes: ["shipping"], + group: "Ground", + enabled: true, + label: "Flat Rate", + name: "flatRates", + fulfillmentMethod: "flatRates", + displayMessageMethod: "Sample display message" + }, + fulfillmentTypeId: "fulfillment123", + shopId: "SHOP_ID" + }); + + expect(result).toEqual({ + group: { + _id: expect.any(String), + cost: 99, + handling: 99, + rate: 99, + fulfillmentTypes: ["shipping"], + group: "Ground", + enabled: true, + label: "Flat Rate", + name: "flatRates", + fulfillmentMethod: "flatRates", + displayMessageMethod: "Sample display message" + } + }); +}); diff --git a/packages/api-plugin-fulfillment/src/mutations/createFulfillmentType.js b/packages/api-plugin-fulfillment/src/mutations/createFulfillmentType.js new file mode 100644 index 00000000000..bd2bcadfcfa --- /dev/null +++ b/packages/api-plugin-fulfillment/src/mutations/createFulfillmentType.js @@ -0,0 +1,38 @@ +import Random from "@reactioncommerce/random"; +import ReactionError from "@reactioncommerce/reaction-error"; +import { fulfillmentTypeSchema } from "../simpleSchemas.js"; + +/** + * @method createFulfillmentType + * @summary updates the selected fulfillment type + * @param {Object} context - an object containing the per-request state + * @param {Object} input - Input (see SimpleSchema) + * @returns {Promise} An object with the updated type + */ +export default async function createFulfillmentType(context, input) { + const cleanedInput = fulfillmentTypeSchema.clean(input); + cleanedInput.provider.name = cleanedInput.name; + + if (cleanedInput.method) { + cleanedInput.method._id = Random.id(); + cleanedInput.method.fulfillmentTypes = [cleanedInput.fulfillmentType]; + } + fulfillmentTypeSchema.validate(cleanedInput); + + const { collections } = context; + const { Fulfillment } = collections; + const { shopId, name, fulfillmentType } = cleanedInput; + + const ffTypeRecord = await Fulfillment.findOne({ name, shopId, fulfillmentType }); + if (ffTypeRecord) throw new ReactionError("invalid-parameter", "Fulfillment Type already exists"); + + await context.validatePermissions("reaction:legacy:fulfillmentTypes", "create", { shopId }); + + const { insertedCount } = await Fulfillment.insertOne({ + _id: Random.id(), + ...cleanedInput + }); + if (insertedCount === 0) throw new ReactionError("server-error", "Unable to create fulfillment type"); + + return { group: { name: cleanedInput.name, fulfillmentType: cleanedInput.fulfillmentType } }; +} diff --git a/packages/api-plugin-fulfillment/src/mutations/index.js b/packages/api-plugin-fulfillment/src/mutations/index.js new file mode 100644 index 00000000000..53b79e27d40 --- /dev/null +++ b/packages/api-plugin-fulfillment/src/mutations/index.js @@ -0,0 +1,15 @@ +import selectFulfillmentOptionForGroup from "./selectFulfillmentOptionForGroup.js"; +import updateFulfillmentOptionsForGroup from "./updateFulfillmentOptionsForGroup.js"; +import updateFulfillmentType from "./updateFulfillmentType.js"; +import createFulfillmentType from "./createFulfillmentType.js"; +import updateFulfillmentMethod from "./updateFulfillmentMethod.js"; +import createFulfillmentMethod from "./createFulfillmentMethod.js"; + +export default { + createFulfillmentType, + updateFulfillmentType, + createFulfillmentMethod, + updateFulfillmentMethod, + selectFulfillmentOptionForGroup, + updateFulfillmentOptionsForGroup +}; diff --git a/packages/api-plugin-fulfillment/src/mutations/selectFulfillmentOptionForGroup.js b/packages/api-plugin-fulfillment/src/mutations/selectFulfillmentOptionForGroup.js new file mode 100644 index 00000000000..f6a56024e21 --- /dev/null +++ b/packages/api-plugin-fulfillment/src/mutations/selectFulfillmentOptionForGroup.js @@ -0,0 +1,57 @@ +import SimpleSchema from "simpl-schema"; +import ReactionError from "@reactioncommerce/reaction-error"; +import getCartById from "../util/getCartById.js"; + +const inputSchema = new SimpleSchema({ + cartId: String, + cartToken: { + type: String, + optional: true + }, + fulfillmentGroupId: String, + fulfillmentMethodId: String +}); + +/** + * @method selectFulfillmentOptionForGroup + * @summary Selects a fulfillment option for a fulfillment group + * @param {Object} context - an object containing the per-request state + * @param {Object} input - an object of all mutation arguments that were sent by the client + * @param {String} input.cartId - The ID of the cart to select a fulfillment option for + * @param {String} [input.cartToken] - The token for the cart, required if it is an anonymous cart + * @param {String} input.fulfillmentGroupId - The group to select a fulfillment option for + * @param {String} input.fulfillmentMethodId - The fulfillment method ID from the option the shopper selected + * @returns {Promise} An object with a `cart` property containing the updated cart + */ +export default async function selectFulfillmentOptionForGroup(context, input) { + const cleanedInput = inputSchema.clean(input || {}); + + inputSchema.validate(cleanedInput); + + const { cartId, cartToken, fulfillmentGroupId, fulfillmentMethodId } = cleanedInput; + + const cart = await getCartById(context, cartId, { cartToken, throwIfNotFound: true }); + + const fulfillmentGroup = (cart.shipping || []).find((group) => group._id === fulfillmentGroupId); + if (!fulfillmentGroup) throw new ReactionError("not-found", `Fulfillment group with ID ${fulfillmentGroupId} not found in cart with ID ${cartId}`); + + // Make sure there is an option for this group that has the requested ID + const option = (fulfillmentGroup.shipmentQuotes || []).find((quote) => quote.method._id === fulfillmentMethodId); + if (!option) throw new ReactionError("not-found", `Fulfillment option with method ID ${fulfillmentMethodId} not found in cart with ID ${cartId}`); + + const updatedCart = { + ...cart, + shipping: cart.shipping.map((group) => { + if (group._id === fulfillmentGroupId) { + return { ...group, shipmentMethod: option.method }; + } + + return group; + }), + updatedAt: new Date() + }; + + const savedCart = await context.mutations.saveCart(context, updatedCart); + + return { cart: savedCart }; +} diff --git a/packages/api-plugin-fulfillment/src/mutations/updateFulfillmentMethod.js b/packages/api-plugin-fulfillment/src/mutations/updateFulfillmentMethod.js new file mode 100644 index 00000000000..152c7a215f1 --- /dev/null +++ b/packages/api-plugin-fulfillment/src/mutations/updateFulfillmentMethod.js @@ -0,0 +1,69 @@ +import SimpleSchema from "simpl-schema"; +import ReactionError from "@reactioncommerce/reaction-error"; +import { FulfillmentMethodSchema } from "../simpleSchemas.js"; + +const inputSchema = new SimpleSchema({ + method: FulfillmentMethodSchema, + fulfillmentTypeId: String, + methodId: String, + shopId: String +}); + +/** + * @method updateFulfillmentMethodMutation + * @summary updates a flat rate fulfillment method + * @param {Object} context - an object containing the per-request state + * @param {Object} input - Input (see SimpleSchema) + * @returns {Promise} An object with a `method` property containing the updated method + */ +export default async function updateFulfillmentMethodMutation(context, input) { + const cleanedInput = inputSchema.clean(input); // add default values and such + inputSchema.validate(cleanedInput); + + const { method: inputMethod, methodId, fulfillmentTypeId, shopId } = cleanedInput; + const { collections } = context; + const { Fulfillment } = collections; + const method = { ...inputMethod }; + + if (!fulfillmentTypeId) throw new ReactionError("invalid-parameter", "Fulfillment Type ID to be updated not provided"); + if (!methodId) throw new ReactionError("invalid-parameter", "Method ID to be updated not provided"); + + await context.validatePermissions(`reaction:legacy:fulfillmentMethods:${methodId}`, "update", { shopId }); + + // MongoDB schema still uses `enabled` rather than `isEnabled` + // method.enabled = method.isEnabled; + // delete method.isEnabled; + + const ffTypeMethodRecord = await Fulfillment.findOne({ + "_id": fulfillmentTypeId, + shopId, + "methods._id": methodId + }); + if (!ffTypeMethodRecord) throw new ReactionError("server-error", "Fulfillment Method does not exist"); + + // Do not update the fulfillmentType, fulfillmentMethod, group, name & _id fields + // Find the matching fulfillmentMethod object and use those values to over-write + const currentFulfillmentMethod = (ffTypeMethodRecord.methods || []).find((meth) => meth._id === methodId); + if (!currentFulfillmentMethod) throw new ReactionError("server-error", "Fulfillment Method does not exist"); + const updatedMethod = { + ...method, + _id: methodId, + name: currentFulfillmentMethod.name, + group: currentFulfillmentMethod.group, + fulfillmentMethod: currentFulfillmentMethod.fulfillmentMethod, + fulfillmentType: [ffTypeMethodRecord.fulfillmentType] + }; + + const { matchedCount } = await Fulfillment.updateOne({ + "_id": fulfillmentTypeId, + "methods._id": methodId, + shopId + }, { + $set: { + "methods.$": updatedMethod + } + }); + if (matchedCount === 0) throw new ReactionError("not-found", "Fulfillment type to be updated not found"); + + return { group: updatedMethod }; +} diff --git a/packages/api-plugin-fulfillment/src/mutations/updateFulfillmentOptionsForGroup.js b/packages/api-plugin-fulfillment/src/mutations/updateFulfillmentOptionsForGroup.js new file mode 100644 index 00000000000..ada20aa6637 --- /dev/null +++ b/packages/api-plugin-fulfillment/src/mutations/updateFulfillmentOptionsForGroup.js @@ -0,0 +1,102 @@ +import _ from "lodash"; +import SimpleSchema from "simpl-schema"; +import ReactionError from "@reactioncommerce/reaction-error"; +import getCartById from "../util/getCartById.js"; + +const inputSchema = new SimpleSchema({ + cartId: String, + cartToken: { + type: String, + optional: true + }, + fulfillmentGroupId: String +}); + +/** + * @name getShipmentQuotesQueryStatus + * @param {Array} rates Rate array + * @returns {Object} An object with `shipmentQuotes` and `shipmentQuotesQueryStatus` on it + * @private + */ +function getShipmentQuotesQueryStatus(rates) { + if (rates.length === 0) { + return { + shipmentQuotes: [], + shipmentQuotesQueryStatus: { + requestStatus: "pending" + } + }; + } + + const errorResult = rates.find((option) => option.requestStatus === "error"); + if (errorResult) { + return { + shipmentQuotes: [], + shipmentQuotesQueryStatus: { + requestStatus: errorResult.requestStatus, + shippingProvider: errorResult.shippingProvider, + message: errorResult.message + } + }; + } + + return { + shipmentQuotes: rates, + shipmentQuotesQueryStatus: { + requestStatus: "success", + numOfShippingMethodsFound: rates.length + } + }; +} + +/** + * @method updateFulfillmentOptionsForGroup + * @summary Updates the fulfillment quotes for a fulfillment group + * @param {Object} context - an object containing the per-request state + * @param {Object} input - an object of all mutation arguments that were sent by the client + * @param {String} input.cartId - The ID of the cart to update fulfillment options for + * @param {String} [input.cartToken] - The token for the cart, required if it is an anonymous cart + * @param {String} input.fulfillmentGroupId - The group to update fulfillment options for + * @returns {Promise} An object with a `cart` property containing the updated cart + */ +export default async function updateFulfillmentOptionsForGroup(context, input) { + const cleanedInput = inputSchema.clean(input || {}); + inputSchema.validate(cleanedInput); + + const { cartId, cartToken, fulfillmentGroupId } = cleanedInput; + + const cart = await getCartById(context, cartId, { cartToken, throwIfNotFound: true }); + + // This is done by `saveCart`, too, but we need to do it before every call to `getCommonOrderForCartGroup` + // to avoid errors in the case where a product has been deleted since the last time this cart was saved. + // This mutates that `cart` object. + await context.mutations.removeMissingItemsFromCart(context, cart); + + const fulfillmentGroup = (cart.shipping || []).find((group) => group._id === fulfillmentGroupId); + if (!fulfillmentGroup) throw new ReactionError("not-found", `Fulfillment group with ID ${fulfillmentGroupId} not found in cart with ID ${cartId}`); + + const commonOrder = await context.queries.getCommonOrderForCartGroup(context, { cart, fulfillmentGroupId: fulfillmentGroup._id }); + // In the future we want to do this async and subscribe to the results + const rates = await context.queries.getFulfillmentMethodsWithQuotes(commonOrder, context); + + const { shipmentQuotes, shipmentQuotesQueryStatus } = getShipmentQuotesQueryStatus(rates); + + if (!_.isEqual(shipmentQuotes, fulfillmentGroup.shipmentQuotes) || !_.isEqual(shipmentQuotesQueryStatus, fulfillmentGroup.shipmentQuotesQueryStatus)) { + const updatedCart = { + ...cart, + shipping: cart.shipping.map((group) => { + if (group._id === fulfillmentGroupId) { + return { ...group, shipmentQuotes, shipmentQuotesQueryStatus }; + } + + return group; + }), + updatedAt: new Date() + }; + + const savedCart = await context.mutations.saveCart(context, updatedCart); + + return { cart: savedCart }; + } + return { cart }; +} diff --git a/packages/api-plugin-fulfillment/src/mutations/updateFulfillmentType.js b/packages/api-plugin-fulfillment/src/mutations/updateFulfillmentType.js new file mode 100644 index 00000000000..089c91e0b4c --- /dev/null +++ b/packages/api-plugin-fulfillment/src/mutations/updateFulfillmentType.js @@ -0,0 +1,55 @@ +import SimpleSchema from "simpl-schema"; +import ReactionError from "@reactioncommerce/reaction-error"; + +const inputSchema = new SimpleSchema({ + fulfillmentGroupId: String, + shopId: String, + name: String, + enabled: { + type: Boolean, + defaultValue: true + }, + label: String, + displayMessageType: { + type: String, + optional: true + } +}); + +/** + * @method updateFulfillmentType + * @summary updates the selected fulfillment type + * @param {Object} context - an object containing the per-request state + * @param {Object} input - Input (see SimpleSchema) + * @returns {Promise} An object with the updated type + */ +export default async function updateFulfillmentType(context, input) { + const cleanedInput = inputSchema.clean(input); // add default values and such + inputSchema.validate(cleanedInput); + + const { fulfillmentGroupId, shopId, name, enabled, label, displayMessageType } = cleanedInput; + const { collections } = context; + const { Fulfillment } = collections; + + if (!shopId) throw new ReactionError("invalid-parameter", "Shop ID to be updated not provided"); + if (!fulfillmentGroupId) throw new ReactionError("invalid-parameter", "FulfillmentType ID to be updated not provided"); + if (!name) throw new ReactionError("invalid-parameter", "FulfillmentType Name to be updated not provided"); + + await context.validatePermissions(`reaction:legacy:fulfillmentTypes:${fulfillmentGroupId}`, "update", { shopId }); + + const { matchedCount } = await Fulfillment.updateOne({ + _id: fulfillmentGroupId, + shopId + }, { + $set: { + name, + "provider.enabled": enabled, + "provider.name": name, + "provider.label": label, + displayMessageType + } + }); + if (matchedCount === 0) throw new ReactionError("not-found", "Fulfillment type to update not found"); + + return { group: cleanedInput }; +} diff --git a/packages/api-plugin-fulfillment/src/preStartup.js b/packages/api-plugin-fulfillment/src/preStartup.js new file mode 100644 index 00000000000..a2f7d44ccf5 --- /dev/null +++ b/packages/api-plugin-fulfillment/src/preStartup.js @@ -0,0 +1,21 @@ +import Logger from "@reactioncommerce/logger"; +import ReactionError from "@reactioncommerce/reaction-error"; +import { extendFulfillmentSchemas } from "./simpleSchemas.js"; + +const logCtx = { name: "fulfillment", file: "preStartup" }; + +/** + * @summary Called before startup to extend schemas + * @param {Object} context Startup context + * @returns {undefined} + */ +export default async function fulfillmentPreStartup(context) { + const allFulfillmentTypesArray = context.allRegisteredFulfillmentTypes?.registeredFulfillmentTypes; + + if (!allFulfillmentTypesArray || allFulfillmentTypesArray.length === 0) { + Logger.warn(logCtx, "No fulfillment types available"); + throw new ReactionError("not-configured", "No fulfillment types available"); + } + + extendFulfillmentSchemas(context.simpleSchemas, allFulfillmentTypesArray); +} diff --git a/packages/api-plugin-fulfillment/src/queries/getFulfillmentMethods.js b/packages/api-plugin-fulfillment/src/queries/getFulfillmentMethods.js new file mode 100644 index 00000000000..3731ea54832 --- /dev/null +++ b/packages/api-plugin-fulfillment/src/queries/getFulfillmentMethods.js @@ -0,0 +1,47 @@ +/** + * @name getFulfillmentMethods + * @method + * @memberof Fulfillment/Queries + * @summary Query the Fulfillment collection for a list of fulfillment Methods + * @param {Object} context - an object containing the per-request state + * @param {Object} input - Request input + * @param {String} input.shopId - The shop id of the fulfillment types + * @param {String} input.fulfillmentTypeId - The fulfillmentType id of the fulfillment type + * @returns {Promise} Mongo cursor + */ +export default async function getFulfillmentMethods(context, input) { + const { collections } = context; + const { Fulfillment } = collections; + const { shopId, fulfillmentTypeId } = input; + + await context.validatePermissions("reaction:legacy:fulfillmentTypes", "read", { shopId }); + + // aggregate pipeline to extract fulfillment methods inside Fulfillment collection + return { + collection: Fulfillment, + pipeline: [ + { + $match: { + shopId, + _id: fulfillmentTypeId + } + }, + { + $unwind: "$methods" + }, + { + $replaceRoot: { + newRoot: { + $mergeObjects: [ + "$methods", + { + fulfillmentTypeId: "$$ROOT._id", + shopId: "$$ROOT.shopId" + } + ] + } + } + } + ] + }; +} diff --git a/packages/api-plugin-fulfillment/src/queries/getFulfillmentMethodsWithQuotes.js b/packages/api-plugin-fulfillment/src/queries/getFulfillmentMethodsWithQuotes.js new file mode 100644 index 00000000000..02b1428e6c2 --- /dev/null +++ b/packages/api-plugin-fulfillment/src/queries/getFulfillmentMethodsWithQuotes.js @@ -0,0 +1,50 @@ +import Logger from "@reactioncommerce/logger"; +import ReactionError from "@reactioncommerce/reaction-error"; +import extendCommonOrder from "../util/extendCommonOrder.js"; + +const logCtx = { name: "fulfillment", file: "getFulfillmentWithQuotes" }; + +/** + * @name getFulfillmentMethodsWithQuotes + * @method + * @summary Just gets rates, without updating anything + * @param {Object} commonOrder - details about the purchase a user wants to make. + * @param {Object} context - Context + * @returns {Array} return updated rates in cart + * @private + */ +export default async function getFulfillmentMethodsWithQuotes(commonOrder, context) { + const rates = []; + const retrialTargets = []; + + // must have items to calculate shipping + if (!commonOrder.items || !commonOrder.items.length) { + Logger.debug(logCtx, "getFulfillmentMethodsWithQuotes called with CommonOrder with no items"); + return rates; + } + const commonOrderExtended = await extendCommonOrder(context, commonOrder); + + const fulfillmentTypeInGroup = commonOrder.fulfillmentType; + if (!fulfillmentTypeInGroup) throw new ReactionError("not-found", "Fulfillment type not found in commonOrder"); + + const functionTypesToCall = `getFulfillmentMethodsWithQuotes${fulfillmentTypeInGroup}`; + const funcs = context.getFunctionsOfType(functionTypesToCall); + + if (!funcs || !Array.isArray(funcs) || !funcs.length) throw new ReactionError("not-found", `No methods for Fulfillment type ${fulfillmentTypeInGroup}`); + + let promises = funcs.map((rateFunction) => rateFunction(context, commonOrderExtended, [rates, retrialTargets])); + await Promise.all(promises); + + // Try once more. + if (retrialTargets.length > 0) { + promises = funcs.map((rateFunction) => rateFunction(context, commonOrderExtended, [rates, retrialTargets])); + await Promise.all(promises); + + if (retrialTargets.length > 0) { + Logger.warn({ ...logCtx, retrialTargets }, "Failed to get fulfillment methods from these packages:"); + } + } + + Logger.debug({ ...logCtx, rates }, "getFulfillmentMethodsWithQuotes returning rates"); + return rates; +} diff --git a/packages/api-plugin-fulfillment/src/queries/getFulfillmentType.js b/packages/api-plugin-fulfillment/src/queries/getFulfillmentType.js new file mode 100644 index 00000000000..c2b0596efc2 --- /dev/null +++ b/packages/api-plugin-fulfillment/src/queries/getFulfillmentType.js @@ -0,0 +1,29 @@ +/** + * @name getFulfillmentType + * @method + * @memberof Fulfillment/Queries + * @summary Query the Fulfillment collection for a single fulfillment type + * @param {Object} context - an object containing the per-request state + * @param {Object} input - Request input + * @param {String} input.fulfillmentTypeId - The fulfillment type id + * @param {String} input.shopId - The shop id of the fulfillment type + * @returns {Promise} Mongo cursor + */ +export default async function getFulfillmentType(context, input) { + const { collections } = context; + const { Fulfillment } = collections; + const { fulfillmentTypeId, shopId } = input; + + await context.validatePermissions("reaction:legacy:fulfillmentTypes", "read", { shopId }); + + const doc = await Fulfillment.findOne({ + _id: fulfillmentTypeId, + shopId + }); + if (!doc) return null; + + return { + ...doc, + shopId + }; +} diff --git a/packages/api-plugin-fulfillment/src/queries/getFulfillmentTypes.js b/packages/api-plugin-fulfillment/src/queries/getFulfillmentTypes.js new file mode 100644 index 00000000000..546d01d672f --- /dev/null +++ b/packages/api-plugin-fulfillment/src/queries/getFulfillmentTypes.js @@ -0,0 +1,21 @@ +/** + * @name getFulfillmentTypes + * @method + * @memberof Fulfillment/Queries + * @summary Query the Fulfillment collection for a list of fulfillment types + * @param {Object} context - an object containing the per-request state + * @param {Object} input - Request input + * @param {String} input.shopId - The shop id of the fulfillment types + * @returns {Promise} Mongo cursor + */ +export default async function getFulfillmentTypes(context, input) { + const { collections } = context; + const { Fulfillment } = collections; + const { shopId } = input; + + await context.validatePermissions("reaction:legacy:fulfillmentTypes", "read", { shopId }); + + return Fulfillment.find({ + shopId + }); +} diff --git a/packages/api-plugin-fulfillment/src/queries/index.js b/packages/api-plugin-fulfillment/src/queries/index.js new file mode 100644 index 00000000000..3582994cb92 --- /dev/null +++ b/packages/api-plugin-fulfillment/src/queries/index.js @@ -0,0 +1,12 @@ +// import allFulfillmentTypes from "./allFulfillmentTypes.js"; +import getFulfillmentMethodsWithQuotes from "./getFulfillmentMethodsWithQuotes.js"; +import getFulfillmentType from "./getFulfillmentType.js"; +import getFulfillmentTypes from "./getFulfillmentTypes.js"; +import getFulfillmentMethods from "./getFulfillmentMethods.js"; + +export default { + getFulfillmentType, + getFulfillmentTypes, + getFulfillmentMethods, + getFulfillmentMethodsWithQuotes +}; diff --git a/packages/api-plugin-fulfillment/src/registration.js b/packages/api-plugin-fulfillment/src/registration.js new file mode 100644 index 00000000000..9645f27e9eb --- /dev/null +++ b/packages/api-plugin-fulfillment/src/registration.js @@ -0,0 +1,27 @@ +import SimpleSchema from "simpl-schema"; + +const FulfillmentTypeDeclaration = new SimpleSchema({ + "registeredFulfillmentTypes": { + type: Array + }, + "registeredFulfillmentTypes.$": { + type: String + } +}); + +export const allRegisteredFulfillmentTypes = { + registeredFulfillmentTypes: ["undecided"] +}; + +/** + * @summary Collect all the registered Fulfillment types + * @param {Object} registeredFulfillmentTypes - Fulfillment types passed in via child plugins + * @returns {undefined} undefined + */ +export function registerPluginHandlerForFulfillmentTypes({ registeredFulfillmentTypes }) { + if (registeredFulfillmentTypes) { + allRegisteredFulfillmentTypes.registeredFulfillmentTypes = allRegisteredFulfillmentTypes.registeredFulfillmentTypes.concat(registeredFulfillmentTypes); + } + + FulfillmentTypeDeclaration.validate(allRegisteredFulfillmentTypes); +} diff --git a/packages/api-plugin-fulfillment/src/resolvers/FulfillmentMethod/index.js b/packages/api-plugin-fulfillment/src/resolvers/FulfillmentMethod/index.js new file mode 100644 index 00000000000..1a84788268b --- /dev/null +++ b/packages/api-plugin-fulfillment/src/resolvers/FulfillmentMethod/index.js @@ -0,0 +1,5 @@ +import { encodeFulfillmentMethodOpaqueId } from "../../xforms/id.js"; + +export default { + _id: (node) => encodeFulfillmentMethodOpaqueId(node._id) +}; diff --git a/packages/api-plugin-fulfillment/src/resolvers/Mutation/index.js b/packages/api-plugin-fulfillment/src/resolvers/Mutation/index.js new file mode 100644 index 00000000000..6ae592f0426 --- /dev/null +++ b/packages/api-plugin-fulfillment/src/resolvers/Mutation/index.js @@ -0,0 +1,11 @@ +import selectFulfillmentOptionForGroup from "./selectFulfillmentOptionForGroup.js"; +import updateFulfillmentOptionsForGroup from "./updateFulfillmentOptionsForGroup.js"; +import updateFulfillmentType from "./updateFulfillmentType.js"; +import updateFulfillmentMethod from "./updateFulfillmentMethod.js"; + +export default { + updateFulfillmentMethod, + updateFulfillmentType, + selectFulfillmentOptionForGroup, + updateFulfillmentOptionsForGroup +}; diff --git a/packages/api-plugin-fulfillment/src/resolvers/Mutation/selectFulfillmentOptionForGroup.js b/packages/api-plugin-fulfillment/src/resolvers/Mutation/selectFulfillmentOptionForGroup.js new file mode 100644 index 00000000000..260efda3b50 --- /dev/null +++ b/packages/api-plugin-fulfillment/src/resolvers/Mutation/selectFulfillmentOptionForGroup.js @@ -0,0 +1,43 @@ +import { decodeCartOpaqueId, decodeFulfillmentGroupOpaqueId, decodeFulfillmentMethodOpaqueId } from "../../xforms/id.js"; +import selectFulfillmentOptionForGroupMutation from "../../mutations/selectFulfillmentOptionForGroup.js"; + +/** + * @name Mutation/selectFulfillmentOptionForGroup + * @method + * @memberof Cart/GraphQL + * @summary resolver for the selectFulfillmentOptionForGroup GraphQL mutation + * @param {Object} parentResult - unused + * @param {Object} args.input - an object of all mutation arguments that were sent by the client + * @param {String} args.input.cartId - The ID of the cart to select a fulfillment option for + * @param {String} [args.input.cartToken] - The token for the cart, required if it is an anonymous cart + * @param {String} args.input.fulfillmentGroupId - The group to select a fulfillment option for + * @param {String} args.input.fulfillmentMethodId - The fulfillment method ID from the option the shopper selected + * @param {String} [args.input.clientMutationId] - An optional string identifying the mutation call + * @param {Object} context - an object containing the per-request state + * @returns {Promise} SelectFulfillmentOptionForGroupPayload + */ +export default async function selectFulfillmentOptionForGroup(parentResult, { input }, context) { + const { + cartId: opaqueCartId, + cartToken, + clientMutationId = null, + fulfillmentGroupId: opaqueFulfillmentGroupId, + fulfillmentMethodId: opaqueFulfillmentMethodId + } = input; + + const cartId = decodeCartOpaqueId(opaqueCartId); + const fulfillmentGroupId = decodeFulfillmentGroupOpaqueId(opaqueFulfillmentGroupId); + const fulfillmentMethodId = decodeFulfillmentMethodOpaqueId(opaqueFulfillmentMethodId); + + const { cart } = await selectFulfillmentOptionForGroupMutation(context, { + cartId, + cartToken, + fulfillmentGroupId, + fulfillmentMethodId + }); + + return { + cart, + clientMutationId + }; +} diff --git a/packages/api-plugin-fulfillment/src/resolvers/Mutation/updateFulfillmentMethod.js b/packages/api-plugin-fulfillment/src/resolvers/Mutation/updateFulfillmentMethod.js new file mode 100644 index 00000000000..d99e72074f0 --- /dev/null +++ b/packages/api-plugin-fulfillment/src/resolvers/Mutation/updateFulfillmentMethod.js @@ -0,0 +1,35 @@ +import { decodeShopOpaqueId, decodeFulfillmentGroupOpaqueId, decodeFulfillmentMethodOpaqueId } from "../../xforms/id.js"; +import updateFulfillmentMethodMutation from "../../mutations/updateFulfillmentMethod.js"; +/** + * @name Mutation/updateFulfillmentMethod + * @method + * @memberof Cart/GraphQL + * @summary resolver for the updateFulfillmentMethod GraphQL mutation + * @param {Object} parentResult - unused + * @param {Object} args.input - an object of all mutation arguments that were sent by the client + * @param {String} args.input.fulfillmentTypeId - The fulfillment type to be updated + * @param {String} args.input.methodId - The fulfillment method to be updated + * @param {String} args.input.shopId - The ShopId to which the fulfillment group belongs + * @param {String} args.input.method - The fulfillment method data to be updated + * @param {String} [args.input.clientMutationId] - An optional string identifying the mutation call + * @param {Object} context - an object containing the per-request state + * @returns {Promise} updateFulfillmentMethodPayload + */ +export default async function updateFulfillmentMethod(parentResult, { input }, context) { + const { shopId: opaqueShopId, clientMutationId = null, fulfillmentTypeId: opaqueFulfillmentTypeId, methodId: opaqueMethodId, method } = input.groupInfo; + + const methodId = decodeFulfillmentMethodOpaqueId(opaqueMethodId); + const fulfillmentTypeId = decodeFulfillmentGroupOpaqueId(opaqueFulfillmentTypeId); + const shopId = decodeShopOpaqueId(opaqueShopId); + const { group } = await updateFulfillmentMethodMutation(context, { + shopId, + fulfillmentTypeId, + methodId, + method + }); + + return { + group, + clientMutationId + }; +} diff --git a/packages/api-plugin-fulfillment/src/resolvers/Mutation/updateFulfillmentOptionsForGroup.js b/packages/api-plugin-fulfillment/src/resolvers/Mutation/updateFulfillmentOptionsForGroup.js new file mode 100644 index 00000000000..0045b0dee80 --- /dev/null +++ b/packages/api-plugin-fulfillment/src/resolvers/Mutation/updateFulfillmentOptionsForGroup.js @@ -0,0 +1,32 @@ +import { decodeCartOpaqueId, decodeFulfillmentGroupOpaqueId } from "../../xforms/id.js"; +import updateFulfillmentOptionsForGroupMutation from "../../mutations/updateFulfillmentOptionsForGroup.js"; +/** + * @name Mutation/updateFulfillmentOptionsForGroup + * @method + * @memberof Cart/GraphQL + * @summary resolver for the updateFulfillmentOptionsForGroup GraphQL mutation + * @param {Object} parentResult - unused + * @param {Object} args.input - an object of all mutation arguments that were sent by the client + * @param {String} args.input.cartId - The ID of the cart to update fulfillment options for + * @param {String} [args.input.cartToken] - The token for the cart, required if it is an anonymous cart + * @param {String} args.input.fulfillmentGroupId - The group to update fulfillment options for + * @param {String} [args.input.clientMutationId] - An optional string identifying the mutation call + * @param {Object} context - an object containing the per-request state + * @returns {Promise} UpdateFulfillmentOptionsForGroupPayload + */ +export default async function updateFulfillmentOptionsForGroup(parentResult, { input }, context) { + const { cartId: opaqueCartId, cartToken, clientMutationId = null, fulfillmentGroupId: opaqueFulfillmentGroupId } = input; + + const fulfillmentGroupId = decodeFulfillmentGroupOpaqueId(opaqueFulfillmentGroupId); + const cartId = decodeCartOpaqueId(opaqueCartId); + const { cart } = await updateFulfillmentOptionsForGroupMutation(context, { + cartId, + cartToken, + fulfillmentGroupId + }); + + return { + cart, + clientMutationId + }; +} diff --git a/packages/api-plugin-fulfillment/src/resolvers/Mutation/updateFulfillmentType.js b/packages/api-plugin-fulfillment/src/resolvers/Mutation/updateFulfillmentType.js new file mode 100644 index 00000000000..a19ead70dd8 --- /dev/null +++ b/packages/api-plugin-fulfillment/src/resolvers/Mutation/updateFulfillmentType.js @@ -0,0 +1,34 @@ +import { decodeShopOpaqueId, decodeFulfillmentGroupOpaqueId } from "../../xforms/id.js"; +import updateFulfillmentTypeMutation from "../../mutations/updateFulfillmentType.js"; +/** + * @name Mutation/updateFulfillmentType + * @method + * @memberof Cart/GraphQL + * @summary resolver for the updateFulfillmentType GraphQL mutation + * @param {Object} parentResult - unused + * @param {Object} args.input - an object of all mutation arguments that were sent by the client + * @param {String} args.input.fulfillmentGroupId - The fulfillment group to be updated + * @param {String} args.input.shopId - The ShopId to which the fulfillment group belongs + * @param {String} args.input.name - The fulfillment group name to be updated + * @param {String} args.input.enabled - Flag to enable/disable group + * @param {String} [args.input.clientMutationId] - An optional string identifying the mutation call + * @param {Object} context - an object containing the per-request state + * @returns {Promise} updateFulfillmentTypePayload + */ +export default async function updateFulfillmentType(parentResult, { input }, context) { + const { groupInfo, clientMutationId = null } = input; + const { shopId: opaqueShopId, fulfillmentGroupId: opaqueFulfillmentGroupId } = groupInfo; + + const fulfillmentGroupId = decodeFulfillmentGroupOpaqueId(opaqueFulfillmentGroupId); + const shopId = decodeShopOpaqueId(opaqueShopId); + const { group } = await updateFulfillmentTypeMutation(context, { + ...groupInfo, + shopId, + fulfillmentGroupId + }); + + return { + group, + clientMutationId + }; +} diff --git a/packages/api-plugin-fulfillment/src/resolvers/Query/getFulfillmentMethods.js b/packages/api-plugin-fulfillment/src/resolvers/Query/getFulfillmentMethods.js new file mode 100644 index 00000000000..6ca46d0a053 --- /dev/null +++ b/packages/api-plugin-fulfillment/src/resolvers/Query/getFulfillmentMethods.js @@ -0,0 +1,32 @@ +import getPaginatedResponseFromAggregate from "@reactioncommerce/api-utils/graphql/getPaginatedResponseFromAggregate.js"; +import wasFieldRequested from "@reactioncommerce/api-utils/graphql/wasFieldRequested.js"; +import { decodeShopOpaqueId, decodeFulfillmentGroupOpaqueId } from "../../xforms/id.js"; + +/** + * @name Query/getFulfillmentMethods + * @method + * @memberof Fulfillment/Query + * @summary Query for a list of fulfillment methods + * @param {Object} _ - unused + * @param {Object} args - an object of all arguments that were sent by the client + * @param {String} args.shopId - Shop ID to get records for + * @param {Object} context - an object containing the per-request state + * @param {Object} info Info about the GraphQL request + * @returns {Promise} Fulfillment methods + */ +export default async function getFulfillmentMethods(_, args, context, info) { + const { shopId: opaqueShopId, fulfillmentTypeId: opaqueFulfillmentId, ...connectionArgs } = args; + + const shopId = decodeShopOpaqueId(opaqueShopId); + const fulfillmentTypeId = decodeFulfillmentGroupOpaqueId(opaqueFulfillmentId); + + const { collection, pipeline } = await context.queries.getFulfillmentMethods(context, { + shopId, fulfillmentTypeId + }); + + return getPaginatedResponseFromAggregate(collection, pipeline, connectionArgs, { + includeHasNextPage: wasFieldRequested("pageInfo.hasNextPage", info), + includeHasPreviousPage: wasFieldRequested("pageInfo.hasPreviousPage", info), + includeTotalCount: wasFieldRequested("totalCount", info) + }); +} diff --git a/packages/api-plugin-fulfillment/src/resolvers/Query/getFulfillmentType.js b/packages/api-plugin-fulfillment/src/resolvers/Query/getFulfillmentType.js new file mode 100644 index 00000000000..e1c9bd0f022 --- /dev/null +++ b/packages/api-plugin-fulfillment/src/resolvers/Query/getFulfillmentType.js @@ -0,0 +1,28 @@ +import { decodeFulfillmentGroupOpaqueId, decodeShopOpaqueId } from "../../xforms/id.js"; + +/** + * @name Query/getFulfillmentType + * @method + * @memberof Fulfillment/Query + * @summary Query for a single fulfillment type + * @param {Object} _ - unused + * @param {Object} args - an object of all arguments that were sent by the client + * @param {String} args.fulfillmentTypeId - Fulfillment type ID to get the record of + * @param {String} args.shopId - Shop ID to get record for + * @param {Object} context - an object containing the per-request state + * @returns {Promise} Fulfillment type + */ +export default async function getFulfillmentType(_, args, context) { + const { + fulfillmentTypeId: opaqueTypeId, + shopId: opaqueShopId + } = args; + + const fulfillmentTypeId = decodeFulfillmentGroupOpaqueId(opaqueTypeId); + const shopId = decodeShopOpaqueId(opaqueShopId); + + return context.queries.getFulfillmentType(context, { + fulfillmentTypeId, + shopId + }); +} diff --git a/packages/api-plugin-fulfillment/src/resolvers/Query/getFulfillmentTypes.js b/packages/api-plugin-fulfillment/src/resolvers/Query/getFulfillmentTypes.js new file mode 100644 index 00000000000..f37eb83c2db --- /dev/null +++ b/packages/api-plugin-fulfillment/src/resolvers/Query/getFulfillmentTypes.js @@ -0,0 +1,31 @@ +import getPaginatedResponse from "@reactioncommerce/api-utils/graphql/getPaginatedResponse.js"; +import wasFieldRequested from "@reactioncommerce/api-utils/graphql/wasFieldRequested.js"; +import { decodeShopOpaqueId } from "../../xforms/id.js"; + +/** + * @name Query/FulfillmenTypes + * @method + * @memberof Fulfillment/Query + * @summary Query for a list of fulfillment types + * @param {Object} _ - unused + * @param {Object} args - an object of all arguments that were sent by the client + * @param {String} args.shopId - Shop ID to get records for + * @param {Object} context - an object containing the per-request state + * @param {Object} info Info about the GraphQL request + * @returns {Promise} Fulfillment methods + */ +export default async function getFulfillmentTypes(_, args, context, info) { + const { shopId: opaqueShopId, ...connectionArgs } = args; + + const shopId = decodeShopOpaqueId(opaqueShopId); + + const cursor = await context.queries.getFulfillmentTypes(context, { + shopId + }); + + return getPaginatedResponse(cursor, connectionArgs, { + includeHasNextPage: wasFieldRequested("pageInfo.hasNextPage", info), + includeHasPreviousPage: wasFieldRequested("pageInfo.hasPreviousPage", info), + includeTotalCount: wasFieldRequested("totalCount", info) + }); +} diff --git a/packages/api-plugin-fulfillment/src/resolvers/Query/index.js b/packages/api-plugin-fulfillment/src/resolvers/Query/index.js new file mode 100644 index 00000000000..fa4e54c3eda --- /dev/null +++ b/packages/api-plugin-fulfillment/src/resolvers/Query/index.js @@ -0,0 +1,9 @@ +import getFulfillmentType from "./getFulfillmentType.js"; +import getFulfillmentTypes from "./getFulfillmentTypes.js"; +import getFulfillmentMethods from "./getFulfillmentMethods.js"; + +export default { + getFulfillmentType, + getFulfillmentTypes, + getFulfillmentMethods +}; diff --git a/packages/api-plugin-fulfillment/src/resolvers/index.js b/packages/api-plugin-fulfillment/src/resolvers/index.js new file mode 100644 index 00000000000..63b65a3443d --- /dev/null +++ b/packages/api-plugin-fulfillment/src/resolvers/index.js @@ -0,0 +1,19 @@ +import FulfillmentMethod from "./FulfillmentMethod/index.js"; +import Mutation from "./Mutation/index.js"; +import Query from "./Query/index.js"; + +/** + * Fulfillment related GraphQL resolvers + * @namespace Fulfillment/GraphQL + */ + +export default { + FulfillmentMethod, + Mutation, + Query, + AdditionalData: { + __resolveType(obj) { + return obj.gqlType; + } + } +}; diff --git a/packages/api-plugin-fulfillment/src/schemas/index.js b/packages/api-plugin-fulfillment/src/schemas/index.js new file mode 100644 index 00000000000..30096f92e54 --- /dev/null +++ b/packages/api-plugin-fulfillment/src/schemas/index.js @@ -0,0 +1,5 @@ +import importAsString from "@reactioncommerce/api-utils/importAsString.js"; + +const schema = importAsString("./schema.graphql"); + +export default [schema]; diff --git a/packages/api-plugin-fulfillment/src/schemas/schema.graphql b/packages/api-plugin-fulfillment/src/schemas/schema.graphql new file mode 100644 index 00000000000..5a7369daa09 --- /dev/null +++ b/packages/api-plugin-fulfillment/src/schemas/schema.graphql @@ -0,0 +1,414 @@ +"Allowed fulfillment types, extended by fulfillment-type plugins" +enum FulfillmentType { + "Default fulfillment type when none is decided by user" + undecided +} + +"Default empty object for additionalData" +type emptyData { + gqlType: String + emptyData: Boolean +} + +"Union of Additional Data fields" +union AdditionalData = emptyData + +""" +A single fulfillment method. Fulfillment methods are shown to shoppers along with a quote for them, +and the shopper chooses one method per fulfillment group per cart during checkout. +""" +type FulfillmentMethod implements Node { + "The fulfillment method ID" + _id: ID! + + "A carrier name" + carrier: String + + "The name of this method, for display in the user interface" + displayName: String! + + """ + The fulfillment types for which this method may be used. For example, `shipping` or `digital`. + """ + fulfillmentTypes: [FulfillmentType]! + + "The group to which this method belongs" + group: String + + "The name of this method, a unique identifier" + name: String! + + "Additional data provided by this method" + methodAdditionalData: AdditionalData +} + + +"Defines a fulfillment method in general." +type FulfillmentMethodObj { + "The fulfillment method ID" + _id: ID! + + "The cost of this fulfillment method to the shop, if you track this" + cost: Float + + """ + The fulfillment types for which this method may be used. For example, `shipping` or `digital`. + """ + fulfillmentTypes: [FulfillmentType] + + "The group to which this method belongs" + group: String! + + "A fixed price to charge for handling costs when this fulfillment method is selected for an order" + handling: Float! + + "Include this as a fulfillment option shown to shoppers during checkout?" + enabled: Boolean! + + "The name of this method, for display in the user interface" + label: String! + + "The name of this method, a unique identifier" + name: String! + + "The common-name for this method, to group all variants of the same method" + fulfillmentMethod: String + + "A fixed price to charge for fulfillment costs when this fulfillment method is selected for an order" + rate: Float! +} + +"Provider info" +type ProviderInfo{ + "Name of the provider" + name: String! + + "Label of the provider" + label: String! + + "Flag defining enabled/disabled status" + enabled: Boolean +} +"Fulfillment type root object" +type FulfillmentTypeObj implements Node { + "The Fulfillment Type ID" + _id: ID! + + "The user provided name of the fulfillment type" + name: String! + + "Shop ID" + shopId: ID! + + "ProviderInfo" + provider: ProviderInfo + + "Fulfillment type" + fulfillmentType: String + + "Fulfillment methods" + methods: [FulfillmentMethodObj] +} + +"Custom Fulfillment method object " +type CustomFulfillmentMethodObj implements Node { + "The Fulfillment Type ID" + fulfillmentTypeId: ID! + + "Shop ID" + shopId: ID! + + "The fulfillment method ID" + _id: ID! + + "The cost of this fulfillment method to the shop, if you track this" + cost: Float + + """ + The fulfillment types for which this method may be used. For example, `shipping` or `digital`. + """ + fulfillmentTypes: [FulfillmentType] + + "The group to which this method belongs" + group: String! + + "A fixed price to charge for handling costs when this fulfillment method is selected for an order" + handling: Float! + + "Include this as a fulfillment option shown to shoppers during checkout?" + enabled: Boolean! + + "The name of this method, for display in the user interface" + label: String! + + "The name of this method, a unique identifier" + name: String! + + "The common-name for this method, to group all variants of the same method" + fulfillmentMethod: String + + "A fixed price to charge for fulfillment costs when this fulfillment method is selected for an order" + rate: Float! +} + +"Input needed to select a fulfillment option for a single fulfillment group on a cart" +input updateFulfillmentTypeInputInfo { + "The shop to which this group belongs" + shopId: ID! + + "The group to update" + fulfillmentGroupId: ID! + + "The type name to be updated" + name: String! + + "The label to be updated" + label: String! + + "The displayMessage to be updated" + displayMessageType: String + + "Flag to define if the group should be enabled/disabled" + enabled: Boolean +} +"Input needed to select a fulfillment option for a single fulfillment group on a cart" +input updateFulfillmentTypeInput { + "The shop to which this group belongs" + groupInfo: updateFulfillmentTypeInputInfo! + + "An optional string identifying the mutation call, which will be returned in the response payload" + clientMutationId: String +} +"The updated group info from updateFulfillmentType" +type updateFulfillmentTypeGroup { + "The shop to which this group belongs" + shopId: ID! + + "The group which was updated" + fulfillmentGroupId: ID! + + "The updated group name" + name: String! + + "The updated group label" + label: String! + + "Flag defining enabled/disabled status" + enabled: Boolean +} +"The response from the `updateFulfillmentType` mutation" +type updateFulfillmentTypePayload { + "The updated Group" + group: updateFulfillmentTypeGroup! + + "The same string you sent with the mutation params, for matching mutation calls with their responses" + clientMutationId: String +} + + + +"Method info input" +input MethodInfoInput{ + "Name of the provider" + name: String! + + "Label of the provider" + label: String! + + "Fulfillmenttypes" + fulfillmentTypes: [String]! + + "Group" + group: String! + + "Display message Method" + displayMessageMethod: String + + "Cost" + cost: Int + + "Handling" + handling: Int! + + "Rate" + rate: Int! + + "Flag defining enabled/disabled status" + enabled: Boolean! + + "FulfillmentMethod" + fulfillmentMethod: String +} + + +"The updated group infofrom updateFulfillmentMethod" +input updateFulfillmentMethodInfo { + "Shop Id" + shopId: String! + + "The group which was updated" + fulfillmentTypeId: String! + + "The method which has to be updated" + methodId: String! + + "Method info" + method: MethodInfoInput +} +input updateFulfillmentMethodInput { + "Group Info Fulfillment Method" + groupInfo: updateFulfillmentMethodInfo! + + "An optional string identifying the mutation call, which will be returned in the response payload" + clientMutationId: String +} +type updateFulfillmentMethodPayload { + "The inserted Group" + group: FulfillmentMethodObj! + + "The same string you sent with the mutation params, for matching mutation calls with their responses" + clientMutationId: String +} + +extend type Mutation { + "Updates the Name and Enabled fields for the provided Fulfillment Type" + updateFulfillmentType( + "Mutation input" + input: updateFulfillmentTypeInput! + ): updateFulfillmentTypePayload! + + "Updates the provided Fulfillment Method" + updateFulfillmentMethod( + "Mutation input" + input: updateFulfillmentMethodInput! + ): updateFulfillmentMethodPayload! +} + +extend type Query { + "Get a fulfillment type" + getFulfillmentType( + "Fulfillment type id" + fulfillmentTypeId: ID! + + "Shop ID" + shopId: ID! + ): FulfillmentTypeObj! + + "Get all fulfillment types" + getFulfillmentTypes( + "Shop ID" + shopId: ID! + + "Return only results that come after this cursor. Use this with `first` to specify the number of results to return." + after: ConnectionCursor, + + "Return only results that come before this cursor. Use this with `last` to specify the number of results to return." + before: ConnectionCursor, + + "Return at most this many results. This parameter may be used with either `after` or `offset` parameters." + first: ConnectionLimitInt, + + "Return at most this many results. This parameter may be used with the `before` parameter." + last: ConnectionLimitInt, + + "Return only results that come after the Nth result. This parameter may be used with the `first` parameter." + offset: Int, + ): FulfillmentTypeObjConnection! + + "Get all fulfillment methods for the given type" + getFulfillmentMethods( + "Shop ID" + shopId: ID! + + "Fulfillment Type ID" + fulfillmentTypeId: ID! + + "Return only results that come after this cursor. Use this with `first` to specify the number of results to return." + after: ConnectionCursor, + + "Return only results that come before this cursor. Use this with `last` to specify the number of results to return." + before: ConnectionCursor, + + "Return at most this many results. This parameter may be used with either `after` or `offset` parameters." + first: ConnectionLimitInt, + + "Return at most this many results. This parameter may be used with the `before` parameter." + last: ConnectionLimitInt, + + "Return only results that come after the Nth result. This parameter may be used with the `first` parameter." + offset: Int, + ): FulfillmentMethodObjConnection! +} + + +#### +# Connections +#### + +"A connection edge in which each node is a `FulfillmentTypeObj` object" +type FulfillmentTypeObjEdge { + "The cursor that represents this node in the paginated results" + cursor: ConnectionCursor! + + "The fulfillment method" + node: FulfillmentTypeObj +} + +"A connection edge in which each node is a `CustomFulfillmentMethodObj` object" +type CustomFulfillmentMethodObjEdge { + "The cursor that represents this node in the paginated results" + cursor: ConnectionCursor! + + "The fulfillment method" + node: CustomFulfillmentMethodObj +} + +""" +Wraps a list of FulfillmentTypes, providing pagination cursors and information. + +For information about what Relay-compatible connections are and how to use them, see the following articles: +- [Relay Connection Documentation](https://facebook.github.io/relay/docs/en/graphql-server-specification.html#connections) +- [Relay Connection Specification](https://facebook.github.io/relay/graphql/connections.htm) +- [Using Relay-style Connections With Apollo Client](https://www.apollographql.com/docs/react/recipes/pagination.html) +""" +type FulfillmentTypeObjConnection { + "The list of nodes that match the query, wrapped in an edge to provide a cursor string for each" + edges: [FulfillmentTypeObjEdge] + + """ + You can request the `nodes` directly to avoid the extra wrapping that `NodeEdge` has, + if you know you will not need to paginate the results. + """ + nodes: [FulfillmentTypeObj] + + "Information to help a client request the next or previous page" + pageInfo: PageInfo! + + "The total number of nodes that match your query" + totalCount: Int! +} + +""" +Wraps a list of Cusom FulfillmentMethods, providing pagination cursors and information. + +For information about what Relay-compatible connections are and how to use them, see the following articles: +- [Relay Connection Documentation](https://facebook.github.io/relay/docs/en/graphql-server-specification.html#connections) +- [Relay Connection Specification](https://facebook.github.io/relay/graphql/connections.htm) +- [Using Relay-style Connections With Apollo Client](https://www.apollographql.com/docs/react/recipes/pagination.html) +""" +type FulfillmentMethodObjConnection { + "The list of nodes that match the query, wrapped in an edge to provide a cursor string for each" + edges: [CustomFulfillmentMethodObjEdge] + + """ + You can request the `nodes` directly to avoid the extra wrapping that `NodeEdge` has, + if you know you will not need to paginate the results. + """ + nodes: [CustomFulfillmentMethodObj] + + "Information to help a client request the next or previous page" + pageInfo: PageInfo! + + "The total number of nodes that match your query" + totalCount: Int! +} diff --git a/packages/api-plugin-fulfillment/src/simpleSchemas.js b/packages/api-plugin-fulfillment/src/simpleSchemas.js new file mode 100644 index 00000000000..8ecd68298cd --- /dev/null +++ b/packages/api-plugin-fulfillment/src/simpleSchemas.js @@ -0,0 +1,133 @@ +import SimpleSchema from "simpl-schema"; + +/** + * @name MethodEmptyData + * @memberof Schemas + * @type {SimpleSchema} + * @summary Defines Empty placeholder + * @property {String} gqlType Defines the method type + * @property {Boolean} emptyData Empty Data fields + */ +export const MethodEmptyData = new SimpleSchema({ + gqlType: String, + emptyData: { + type: Boolean, + optional: true + } +}); + + +export const FulfillmentMethodSchema = new SimpleSchema({ + "cost": { + type: Number, + optional: true + }, + "fulfillmentTypes": { + type: Array, + minCount: 1 + }, + "fulfillmentTypes.$": String, + "group": String, + "handling": Number, + "enabled": Boolean, + "label": String, + "name": String, + "fulfillmentMethod": { + type: String, + optional: true + }, + "displayMessageMethod": { + type: String, + optional: true + }, + "rate": Number +}); + +const ProviderSchema = new SimpleSchema({ + enabled: Boolean, + label: String, + name: String +}); + +export const fulfillmentTypeSchema = new SimpleSchema({ + "name": String, + "shopId": String, + "provider": { + type: ProviderSchema + }, + "fulfillmentType": String, + "displayMessageType": { + type: String, + optional: true + }, + "methods": { + type: Array, + optional: true + }, + "methods.$": { + type: FulfillmentMethodSchema + } +}); + +/** + * @summary Extend the schema with updated allowedValues + * @param {Object} schemas Schemas from context passed in + * @param {String[]} allFulfillmentTypesArray Array of all fulfillment types + * @returns {undefined} + */ +export function extendFulfillmentSchemas(schemas, allFulfillmentTypesArray) { + const schemaProductExtension = { + "supportedFulfillmentTypes.$": { + allowedValues: allFulfillmentTypesArray + } + }; + schemas.Product.extend(schemaProductExtension); + + const schemaCatalogProductExtension = { + "supportedFulfillmentTypes.$": { + allowedValues: allFulfillmentTypesArray + } + }; + schemas.CatalogProduct.extend(schemaCatalogProductExtension); + + const schemaShipmentExtension = { + type: { + allowedValues: allFulfillmentTypesArray + } + }; + schemas.Shipment.extend(schemaShipmentExtension); + + const schemaCartItemExtension = { + "selectedFulfillmentType": { + allowedValues: allFulfillmentTypesArray + }, + "supportedFulfillmentTypes.$": { + allowedValues: allFulfillmentTypesArray + } + }; + schemas.CartItem.extend(schemaCartItemExtension); + + const schemaExtension = { + type: { + allowedValues: allFulfillmentTypesArray + } + }; + const schemaExtensionCommonOrder = { + fulfillmentType: { + allowedValues: allFulfillmentTypesArray + } + }; + schemas.CommonOrder.extend(schemaExtensionCommonOrder); + schemas.orderFulfillmentGroupInputSchema.extend(schemaExtension); + schemas.OrderFulfillmentGroup.extend(schemaExtension); + + const schemaExtensionMethodAdditionalData = { + methodAdditionalData: { + type: SimpleSchema.oneOf(MethodEmptyData), + optional: true, + label: "Method specific additional data" + } + }; + schemas.SelectedFulfillmentOption.extend(schemaExtensionMethodAdditionalData); + schemas.ShippingMethod.extend(schemaExtensionMethodAdditionalData); +} diff --git a/packages/api-plugin-fulfillment/src/startup.js b/packages/api-plugin-fulfillment/src/startup.js new file mode 100644 index 00000000000..509d20ea20d --- /dev/null +++ b/packages/api-plugin-fulfillment/src/startup.js @@ -0,0 +1,30 @@ +/** + * @summary Called on startup to creates root entry for undecided fulfillment type + * @param {Object} context Startup context + * @param {Object} context.collections Map of MongoDB collections + * @returns {undefined} + */ +export default async function fulfillmentTypeUndecidedStartup(context) { + const { collections } = context; + const { Fulfillment } = collections; + + context.appEvents.on("afterShopCreate", async (payload) => { + const { shop } = payload; + const shopId = shop._id; + + const undecidedRecord = await Fulfillment.findOne({ fulfillmentType: "undecided", shopId }); + if (!undecidedRecord) { + const groupInfo = { + name: "Undecided Group", + shopId, + provider: { + enabled: true, + label: "Undecided", + name: "undecided" + }, + fulfillmentType: "undecided" + }; + await context.mutations.createFulfillmentType(context.getInternalContext(), groupInfo); + } + }); +} diff --git a/packages/api-plugin-fulfillment/src/tests/factory.js b/packages/api-plugin-fulfillment/src/tests/factory.js new file mode 100644 index 00000000000..427e1346b63 --- /dev/null +++ b/packages/api-plugin-fulfillment/src/tests/factory.js @@ -0,0 +1,20 @@ +import { createFactoryForSchema, Factory } from "@reactioncommerce/data-factory"; + +import { + Cart, + ShipmentQuote +} from "@reactioncommerce/api-plugin-carts/src/simpleSchemas.js"; + +const schemasToAddToFactory = { + Cart, + ShipmentQuote +}; + +// Adds each to `Factory` object. For example, `Factory.Cart` +// will be the factory that builds an object that matches the +// `Cart` schema. +Object.keys(schemasToAddToFactory).forEach((key) => { + createFactoryForSchema(key, schemasToAddToFactory[key]); +}); + +export default Factory; diff --git a/packages/api-plugin-fulfillment/src/util/extendCommonOrder.js b/packages/api-plugin-fulfillment/src/util/extendCommonOrder.js new file mode 100644 index 00000000000..b9a019d408a --- /dev/null +++ b/packages/api-plugin-fulfillment/src/util/extendCommonOrder.js @@ -0,0 +1,93 @@ +import _ from "lodash"; +import ReactionError from "@reactioncommerce/reaction-error"; +import tagsForCatalogProducts from "@reactioncommerce/api-utils/tagsForCatalogProducts.js"; + +/** + * @name mergeProductAndVariants + * @summary Merges a product and its variants + * @param {Object} productAndVariants - The product and its variants + * @returns {Object} - The merged product and variants + */ +function mergeProductAndVariants(productAndVariants) { + const { product, parentVariant, variant } = productAndVariants; + + // Filter out unnecessary product props + const productProps = _.omit(product, [ + "variants", "media", "metafields", "parcel", " primaryImage", "socialMetadata", "customAttributes" + ]); + + // Filter out unnecessary variant props + const variantExcludeProps = ["media", "parcel", "primaryImage", "customAttributes"]; + const variantProps = _.omit(variant, variantExcludeProps); + + // If an option has been added to the cart + if (parentVariant) { + // Filter out unnecessary parent variant props + const parentVariantProps = _.omit(parentVariant, variantExcludeProps); + + return { + ...productProps, + ...parentVariantProps, + ...variantProps + }; + } + + return { + ...productProps, + ...variantProps + }; +} + +/** + * @name extendCommonOrder + * @summary Add extra properties to all CommonOrder items, which will be used to + * determine any applicable shipping restrictions. + * @param {Object} context - an object containing the per-request state + * @param {Object} commonOrder - details about the purchase a user wants to make. + * @returns {Object|null} The CommonOrder, with each item in `.items` extended. + */ +export default async function extendCommonOrder(context, commonOrder) { + const { collections: { Tags }, getFunctionsOfType, queries } = context; + const { items: orderItems } = commonOrder; + const extendedOrderItems = []; + + // Products in the Catalog collection are the source of truth, therefore use them + // as the source of data instead of what is coming from the client. + const catalogProductsAndVariants = await queries.findCatalogProductsAndVariants(context, orderItems); + const allProductsTags = await tagsForCatalogProducts(Tags, catalogProductsAndVariants); + + for (const orderLineItem of orderItems) { + const productAndVariants = catalogProductsAndVariants.find((catProduct) => catProduct.product.productId === orderLineItem.productId); + + if (!productAndVariants) { + throw new ReactionError("not-found", "Catalog product not found"); + } + + const extendedOrderItem = { + ...orderLineItem, + ...mergeProductAndVariants(productAndVariants) + }; + + // Fetch product tags + const tagInfo = allProductsTags.find((info) => info.productId === extendedOrderItem.productId); + if (tagInfo) { + extendedOrderItem.tags = tagInfo.tags; + } + + // Fetch custom attributes + // We need to run each of these functions in a series, rather than in parallel, because + // we are mutating the same object on each pass. It is recommended to disable `no-await-in-loop` + // eslint rules when the output of one iteration might be used as input in another iteration, such as this case here. + // See https://eslint.org/docs/rules/no-await-in-loop#when-not-to-use-it + for (const customAttributesFunc of getFunctionsOfType("addShippingRestrictionCustomAttributes")) { + await customAttributesFunc(extendedOrderItem, productAndVariants); // eslint-disable-line + } + + extendedOrderItems.push(extendedOrderItem); + } + + return { + ...commonOrder, + items: extendedOrderItems + }; +} diff --git a/packages/api-plugin-fulfillment/src/util/getCartById.js b/packages/api-plugin-fulfillment/src/util/getCartById.js new file mode 100644 index 00000000000..3142f9d4f69 --- /dev/null +++ b/packages/api-plugin-fulfillment/src/util/getCartById.js @@ -0,0 +1,34 @@ +import hashToken from "@reactioncommerce/api-utils/hashToken.js"; +import ReactionError from "@reactioncommerce/reaction-error"; + +/** + * @summary Gets a cart from the db by ID. If there is an account for the request, verifies that the + * account has permission to access the cart. Optionally throws an error if not found. + * @param {Object} context Object defining the request state + * @param {String} cartId The cart ID + * @param {Object} [options] Options + * @param {String} [options.cartToken] Cart token, required if it's an anonymous cart + * @param {Boolean} [options.throwIfNotFound] Default false. Throw a not-found error rather than return null `cart` + * @returns {Object|null} The cart document, or null if not found and `throwIfNotFound` was false + */ +export default async function getCartById(context, cartId, { cartToken, throwIfNotFound = false } = {}) { + const { accountId, collections } = context; + const { Cart } = collections; + + const selector = { _id: cartId }; + + if (cartToken) { + selector.anonymousAccessToken = hashToken(cartToken); + } + + const cart = await Cart.findOne(selector); + if (!cart && throwIfNotFound) { + throw new ReactionError("not-found", "Cart not found"); + } + + if (cart && cart.accountId && cart.accountId !== accountId) { + throw new ReactionError("access-denied", "Access Denied"); + } + + return cart || null; +} diff --git a/packages/api-plugin-fulfillment/src/xforms/id.js b/packages/api-plugin-fulfillment/src/xforms/id.js new file mode 100644 index 00000000000..5697bf0747d --- /dev/null +++ b/packages/api-plugin-fulfillment/src/xforms/id.js @@ -0,0 +1,18 @@ +import decodeOpaqueIdForNamespace from "@reactioncommerce/api-utils/decodeOpaqueIdForNamespace.js"; +import encodeOpaqueId from "@reactioncommerce/api-utils/encodeOpaqueId.js"; + +const namespaces = { + Shop: "reaction/shop", + Cart: "reaction/cart", + FulfillmentGroup: "reaction/fulfillmentGroup", + FulfillmentMethod: "reaction/fulfillmentMethod" +}; + +export const encodeShopOpaqueId = encodeOpaqueId(namespaces.Shop); +export const encodeFulfillmentMethodOpaqueId = encodeOpaqueId(namespaces.FulfillmentMethod); +export const encodeFulfillmentGroupOpaqueId = encodeOpaqueId(namespaces.FulfillmentGroup); + +export const decodeShopOpaqueId = decodeOpaqueIdForNamespace(namespaces.Shop); +export const decodeCartOpaqueId = decodeOpaqueIdForNamespace(namespaces.Cart); +export const decodeFulfillmentGroupOpaqueId = decodeOpaqueIdForNamespace(namespaces.FulfillmentGroup); +export const decodeFulfillmentMethodOpaqueId = decodeOpaqueIdForNamespace(namespaces.FulfillmentMethod); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index bda503c6c4d..3349175e78c 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -225,16 +225,16 @@ importers: '@reactioncommerce/api-plugin-payments-example': link:../../packages/api-plugin-payments-example '@reactioncommerce/api-plugin-payments-stripe-sca': link:../../packages/api-plugin-payments-stripe-sca '@reactioncommerce/api-plugin-pricing-simple': link:../../packages/api-plugin-pricing-simple - '@reactioncommerce/api-plugin-products': link:../../packages/api-plugin-products + '@reactioncommerce/api-plugin-products': 1.3.0_graphql@14.7.0 '@reactioncommerce/api-plugin-settings': link:../../packages/api-plugin-settings '@reactioncommerce/api-plugin-shipments': link:../../packages/api-plugin-shipments '@reactioncommerce/api-plugin-shipments-flat-rate': link:../../packages/api-plugin-shipments-flat-rate '@reactioncommerce/api-plugin-shops': link:../../packages/api-plugin-shops '@reactioncommerce/api-plugin-simple-schema': link:../../packages/api-plugin-simple-schema '@reactioncommerce/api-plugin-sitemap-generator': link:../../packages/api-plugin-sitemap-generator - '@reactioncommerce/api-plugin-surcharges': link:../../packages/api-plugin-surcharges + '@reactioncommerce/api-plugin-surcharges': 1.1.7_graphql@14.7.0 '@reactioncommerce/api-plugin-system-information': link:../../packages/api-plugin-system-information - '@reactioncommerce/api-plugin-tags': link:../../packages/api-plugin-tags + '@reactioncommerce/api-plugin-tags': 1.2.0_graphql@14.7.0 '@reactioncommerce/api-plugin-taxes': link:../../packages/api-plugin-taxes '@reactioncommerce/api-plugin-taxes-flat-rate': link:../../packages/api-plugin-taxes-flat-rate '@reactioncommerce/api-plugin-translations': link:../../packages/api-plugin-translations @@ -245,7 +245,7 @@ importers: '@reactioncommerce/logger': link:../../packages/logger '@reactioncommerce/nodemailer': 5.0.5 '@reactioncommerce/random': link:../../packages/random - '@snyk/protect': 1.1012.0 + '@snyk/protect': 1.1032.0 graphql: 14.7.0 semver: 6.3.0 sharp: 0.29.3 @@ -485,7 +485,7 @@ importers: simpl-schema: ^1.12.0 dependencies: '@reactioncommerce/api-plugin-address-validation': link:../api-plugin-address-validation - '@reactioncommerce/api-plugin-tags': link:../api-plugin-tags + '@reactioncommerce/api-plugin-tags': 1.2.0_graphql@15.8.0 '@reactioncommerce/api-utils': link:../api-utils '@reactioncommerce/logger': link:../logger '@reactioncommerce/random': link:../random @@ -673,6 +673,27 @@ importers: '@reactioncommerce/babel-remove-es-create-require': 1.0.0_@babel+core@7.19.0 '@reactioncommerce/data-factory': 1.0.1 + packages/api-plugin-fulfillment: + specifiers: + '@reactioncommerce/api-plugin-carts': ^1.0.0 + '@reactioncommerce/api-utils': ^1.16.9 + '@reactioncommerce/data-factory': ~1.0.1 + '@reactioncommerce/logger': ^1.1.3 + '@reactioncommerce/random': ^1.0.2 + '@reactioncommerce/reaction-error': ^1.0.1 + lodash: ^4.17.21 + simpl-schema: ^1.12.2 + dependencies: + '@reactioncommerce/api-utils': link:../api-utils + '@reactioncommerce/logger': link:../logger + '@reactioncommerce/random': link:../random + '@reactioncommerce/reaction-error': link:../reaction-error + lodash: 4.17.21 + simpl-schema: 1.12.3 + devDependencies: + '@reactioncommerce/api-plugin-carts': link:../api-plugin-carts + '@reactioncommerce/data-factory': 1.0.1 + packages/api-plugin-i18n: specifiers: '@reactioncommerce/api-utils': ^1.16.5 @@ -4591,6 +4612,108 @@ packages: /@protobufjs/utf8/1.1.0: resolution: {integrity: sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw==} + /@reactioncommerce/api-plugin-products/1.3.0_graphql@14.7.0: + resolution: {integrity: sha512-utK/z9MXJ7qcASwvUlFuej5O5fIDUBi5kWg90am91H5p0wa8NKLXNsVzV/MC8oQDSq+/ljhtAhZQsPOBajxr1g==} + engines: {node: '>=14.18.1'} + dependencies: + '@reactioncommerce/api-utils': 1.16.9_graphql@14.7.0 + '@reactioncommerce/logger': 1.1.5 + '@reactioncommerce/random': 1.0.2 + '@reactioncommerce/reaction-error': 1.0.1 + lodash: 4.17.21 + simpl-schema: 1.12.3 + transitivePeerDependencies: + - graphql + dev: false + + /@reactioncommerce/api-plugin-surcharges/1.1.7_graphql@14.7.0: + resolution: {integrity: sha512-UdaWUmFEUHfCaOAGHs31S3yZGufmD7zO+GpOTlC4U310oQjr+CNQ0b2H8QxmSoUKnjgQfO9SN9bjiF5qUcOU6g==} + engines: {node: '>=14.18.1'} + dependencies: + '@reactioncommerce/api-utils': 1.16.9_graphql@14.7.0 + '@reactioncommerce/random': 1.0.2 + '@reactioncommerce/reaction-error': 1.0.1 + simpl-schema: 1.12.3 + transitivePeerDependencies: + - graphql + dev: false + + /@reactioncommerce/api-plugin-tags/1.2.0_graphql@14.7.0: + resolution: {integrity: sha512-6SgTt+AvGJs/ThRq8tMEiEF0HvWShJhzRNjInM+CJ/mz/1U9fDVRxhecwWI7iYG9gqs30XNFRhjgNdJxJVO0SA==} + engines: {node: '>=14.18.1'} + dependencies: + '@reactioncommerce/api-utils': 1.16.9_graphql@14.7.0 + '@reactioncommerce/data-factory': 1.0.1 + '@reactioncommerce/db-version-check': 1.0.0 + '@reactioncommerce/file-collections': 0.9.3 + '@reactioncommerce/random': 1.0.2 + '@reactioncommerce/reaction-error': 1.0.1 + lodash: 4.17.21 + simpl-schema: 1.10.2 + transitivePeerDependencies: + - encoding + - graphql + - supports-color + dev: false + + /@reactioncommerce/api-plugin-tags/1.2.0_graphql@15.8.0: + resolution: {integrity: sha512-6SgTt+AvGJs/ThRq8tMEiEF0HvWShJhzRNjInM+CJ/mz/1U9fDVRxhecwWI7iYG9gqs30XNFRhjgNdJxJVO0SA==} + engines: {node: '>=14.18.1'} + dependencies: + '@reactioncommerce/api-utils': 1.16.9_graphql@15.8.0 + '@reactioncommerce/data-factory': 1.0.1 + '@reactioncommerce/db-version-check': 1.0.0 + '@reactioncommerce/file-collections': 0.9.3 + '@reactioncommerce/random': 1.0.2 + '@reactioncommerce/reaction-error': 1.0.1 + lodash: 4.17.21 + simpl-schema: 1.10.2 + transitivePeerDependencies: + - encoding + - graphql + - supports-color + dev: false + + /@reactioncommerce/api-utils/1.16.9_graphql@14.7.0: + resolution: {integrity: sha512-9jB684K3GISSkkTeiMIniYiFgAtHL1AosSD2GPVpGSpFIUVbEL93e8owEadSIugmJt8yOb3m1+YdThW+kvXlzQ==} + engines: {node: '>=12.14.1'} + dependencies: + '@jest/globals': 26.6.2 + '@reactioncommerce/logger': 1.1.5 + '@reactioncommerce/random': 1.0.2 + '@reactioncommerce/reaction-error': 1.0.1 + accounting-js: 1.1.1 + callsite: 1.0.0 + envalid: 6.0.2 + graphql-fields: 2.0.3 + graphql-relay: 0.9.0_graphql@14.7.0 + lodash: 4.17.21 + ramda: 0.27.2 + transliteration: 2.3.5 + transitivePeerDependencies: + - graphql + dev: false + + /@reactioncommerce/api-utils/1.16.9_graphql@15.8.0: + resolution: {integrity: sha512-9jB684K3GISSkkTeiMIniYiFgAtHL1AosSD2GPVpGSpFIUVbEL93e8owEadSIugmJt8yOb3m1+YdThW+kvXlzQ==} + engines: {node: '>=12.14.1'} + dependencies: + '@jest/globals': 26.6.2 + '@reactioncommerce/logger': 1.1.5 + '@reactioncommerce/random': 1.0.2 + '@reactioncommerce/reaction-error': 1.0.1 + accounting-js: 1.1.1 + callsite: 1.0.0 + envalid: 6.0.2 + graphql-fields: 2.0.3 + graphql-relay: 0.9.0_graphql@15.8.0 + lodash: 4.17.21 + ramda: 0.27.2 + transliteration: 2.3.5 + transitivePeerDependencies: + - graphql + dev: false + /@reactioncommerce/babel-remove-es-create-require/1.0.0_@babel+core@7.19.0: resolution: {integrity: sha512-yR1vMj76hK5D9/VcXjFpk2OMoYpQdbvEgGcJ79cEzTL/jkFwIs53Zl6PNBjcC608P8qHl5eypEYRwG7CCGLzwQ==} peerDependencies: @@ -4621,6 +4744,10 @@ packages: transitivePeerDependencies: - supports-color + /@reactioncommerce/db-version-check/1.0.0: + resolution: {integrity: sha512-68ypfiCvdsmMaLmzNpXjN52SmgyHHO2IjLOscBbqBlvq/rFCVLLxV5/WkiFnedmNesFoXCfwYhyGWyjwaqicuw==} + dev: false + /@reactioncommerce/eslint-config/2.2.2_ihki3xsivcrciip6mkoznh225e: resolution: {integrity: sha512-E4BlfLmQBr5Sjt3LP9tAbgCKXZ2rQ4/Lpvle6khHUcuh33NcuBEInN+uStY6koPHAvnKgwn6MZ0LTYzVnKwSVA==} engines: {node: '>=8.1.0'} @@ -4637,7 +4764,7 @@ packages: dependencies: eslint: 8.23.1 eslint-plugin-import: 2.25.4_eslint@8.23.1 - eslint-plugin-jest: 26.9.0_eslint@8.23.1 + eslint-plugin-jest: 26.9.0_2ex7m26yair3ztqnyc2u7licva eslint-plugin-jsx-a11y: 6.5.1_eslint@8.23.1 eslint-plugin-node: 11.1.0_eslint@8.23.1 eslint-plugin-promise: 6.0.1_eslint@8.23.1 @@ -4646,11 +4773,43 @@ packages: eslint-plugin-you-dont-need-lodash-underscore: 6.12.0 dev: true + /@reactioncommerce/file-collections/0.9.3: + resolution: {integrity: sha512-8HLEjvlhtgpOAQYQeHA6UGYJfbYoSuUtQAyo0E0DUUpgeViXiWgQ+T4DIK3NbUinlvA4Ny8FDtZCNEg5eHCfMw==} + dependencies: + '@babel/runtime-corejs2': 7.19.0 + content-disposition: 0.5.4 + debug: 4.3.4 + extend: 3.0.2 + path-parser: 6.1.0 + query-string: 6.14.1 + tus-js-client: 1.8.0 + tus-node-server: 0.3.2 + transitivePeerDependencies: + - encoding + - supports-color + dev: false + + /@reactioncommerce/logger/1.1.5: + resolution: {integrity: sha512-fG6wohv9NoX6ZjNzs0YcFRFxHyndO4VitMCrbz+8HrnM7LNJtQRzrUcR58RptagYixZ4GyiHY6WhmIfrMpNvLw==} + dependencies: + bunyan: 1.8.15 + bunyan-format: 0.2.1 + node-loggly-bulk: 2.2.5 + dev: false + /@reactioncommerce/nodemailer/5.0.5: resolution: {integrity: sha512-u4ontTETlROmLglkMDyouMXlX62NXOGfOUAd75Ilk3W4tcsRjRXX+g5C5B4mBCCcJB0wHn1yh/a4pOYkn81vUQ==} engines: {node: '>=4.0.0'} dev: false + /@reactioncommerce/random/1.0.2: + resolution: {integrity: sha512-02eARZh6FQ45tEXXZUwnz90XWQasLpnV140CcuOl/L3dhEL4Emu2oCZemVhKvNb2JMmm5vOG2Y0CUb12xAx73w==} + dev: false + + /@reactioncommerce/reaction-error/1.0.1: + resolution: {integrity: sha512-l1HyDTFw4m1j8EPWNA/bbF2GZZaLYQjwmgniSjjM1cgnhfjCqeLywi501oPkmH8E84Jh0EH+4ADhNCcZ4m4gug==} + dev: false + /@sinclair/typebox/0.24.41: resolution: {integrity: sha512-TJCgQurls4FipFvHeC+gfAzb+GGstL0TDwYJKQVtTeSvJIznWzP7g3bAd5gEBlr8+bIxqnWS9VGVWREDhmE8jA==} dev: true @@ -4666,8 +4825,8 @@ packages: '@sinonjs/commons': 1.8.3 dev: false - /@snyk/protect/1.1012.0: - resolution: {integrity: sha512-FetJA4igmHDYLcxq0dvSolgCWBAWvut1lhY8aDbwZDw+dMd7sXkjb4Dl1F7XzRDqUt3wjfyb73OJjSAn1ADZHg==} + /@snyk/protect/1.1032.0: + resolution: {integrity: sha512-C6EvuhDycvmyMCmhjC/KAV1z3aykVGKrYhGRI5u5Buzz27HgjHhB4DSC3MDpyljpahtAkCS/E7pAwo9zP3m4bA==} engines: {node: '>=10'} hasBin: true dev: false @@ -4962,26 +5121,6 @@ packages: engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} dev: true - /@typescript-eslint/typescript-estree/5.37.0: - resolution: {integrity: sha512-JkFoFIt/cx59iqEDSgIGnQpCTRv96MQnXCYvJi7QhBC24uyuzbD8wVbajMB1b9x4I0octYFJ3OwjAwNqk1AjDA==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - peerDependencies: - typescript: '*' - peerDependenciesMeta: - typescript: - optional: true - dependencies: - '@typescript-eslint/types': 5.37.0 - '@typescript-eslint/visitor-keys': 5.37.0 - debug: 4.3.4 - globby: 11.1.0 - is-glob: 4.0.3 - semver: 7.3.7 - tsutils: 3.21.0 - transitivePeerDependencies: - - supports-color - dev: true - /@typescript-eslint/typescript-estree/5.37.0_typescript@2.9.2: resolution: {integrity: sha512-JkFoFIt/cx59iqEDSgIGnQpCTRv96MQnXCYvJi7QhBC24uyuzbD8wVbajMB1b9x4I0octYFJ3OwjAwNqk1AjDA==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} @@ -5003,7 +5142,7 @@ packages: - supports-color dev: true - /@typescript-eslint/utils/5.37.0_eslint@8.23.1: + /@typescript-eslint/utils/5.37.0_2ex7m26yair3ztqnyc2u7licva: resolution: {integrity: sha512-jUEJoQrWbZhmikbcWSMDuUSxEE7ID2W/QCV/uz10WtQqfOuKZUqFGjqLJ+qhDd17rjgp+QJPqTdPIBWwoob2NQ==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} peerDependencies: @@ -5012,7 +5151,7 @@ packages: '@types/json-schema': 7.0.11 '@typescript-eslint/scope-manager': 5.37.0 '@typescript-eslint/types': 5.37.0 - '@typescript-eslint/typescript-estree': 5.37.0 + '@typescript-eslint/typescript-estree': 5.37.0_typescript@2.9.2 eslint: 8.23.1 eslint-scope: 5.1.1 eslint-utils: 3.0.0_eslint@8.23.1 @@ -7786,7 +7925,7 @@ packages: - supports-color dev: true - /eslint-plugin-jest/26.9.0_eslint@8.23.1: + /eslint-plugin-jest/26.9.0_2ex7m26yair3ztqnyc2u7licva: resolution: {integrity: sha512-TWJxWGp1J628gxh2KhaH1H1paEdgE2J61BBF1I59c6xWeL5+D1BzMxGDN/nXAfX+aSkR5u80K+XhskK6Gwq9ng==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} peerDependencies: @@ -7799,7 +7938,7 @@ packages: jest: optional: true dependencies: - '@typescript-eslint/utils': 5.37.0_eslint@8.23.1 + '@typescript-eslint/utils': 5.37.0_2ex7m26yair3ztqnyc2u7licva eslint: 8.23.1 transitivePeerDependencies: - supports-color @@ -9033,6 +9172,15 @@ packages: graphql: 14.7.0 dev: false + /graphql-relay/0.9.0_graphql@15.8.0: + resolution: {integrity: sha512-yNJLCqcjz0XpzpmmckRJCSK8a2ZLwTurwrQ09UyGftONh52PbrGpK1UO4yspvj0c7pC+jkN4ZUqVXG3LRrWkXQ==} + engines: {node: ^12.20.0 || ^14.15.0 || >= 15.9.0} + peerDependencies: + graphql: ^15.5.3 + dependencies: + graphql: 15.8.0 + dev: false + /graphql-schema-linter/3.0.0_graphql@16.6.0: resolution: {integrity: sha512-nXsyFvcqrD9/QWgimvJjvRBsUXHw8O7XoPLhpnGZd9eX6r2ZXsOBCGhTzF9ZrFtWosCBEfBGU2xpFUPe0qCHVg==} hasBin: true @@ -9084,6 +9232,11 @@ packages: dependencies: iterall: 1.3.0 + /graphql/15.8.0: + resolution: {integrity: sha512-5gghUc24tP9HRznNpV2+FIoq3xKkj5dTQqf4v0CpdPbFVwFkWoxOM+o+2OC9ZSvjEMTjfmG9QT+gcvggTwW1zw==} + engines: {node: '>= 10.x'} + dev: false + /graphql/16.6.0: resolution: {integrity: sha512-KPIBPDlW7NxrbT/eh4qPXz5FiFdL5UbaA0XUNz2Rp3Z3hqBSkbj0GVjwFDztsWVauZUWsbKHgMg++sk8UX0bkw==} engines: {node: ^12.22.0 || ^14.16.0 || ^16.0.0 || >=17.0.0} @@ -12378,6 +12531,10 @@ packages: engines: {node: '>=8'} dev: false + /ramda/0.27.2: + resolution: {integrity: sha512-SbiLPU40JuJniHexQSAgad32hfwd+DRUdwF2PlVuI5RZD0/vahUco7R8vD86J/tcEKKF9vZrUVwgtmGCqlCKyA==} + dev: false + /ramda/0.28.0: resolution: {integrity: sha512-9QnLuG/kPVgWvMQ4aODhsBUFKOUmnbUnsSXACv+NCQZcHbeb+v8Lodp8OVxtRULN1/xOyYLLaL6npE6dMq5QTA==} dev: false @@ -13862,15 +14019,6 @@ packages: /tslib/2.4.0: resolution: {integrity: sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ==} - /tsutils/3.21.0: - resolution: {integrity: sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==} - engines: {node: '>= 6'} - peerDependencies: - typescript: '>=2.8.0 || >= 3.2.0-dev || >= 3.3.0-dev || >= 3.4.0-dev || >= 3.5.0-dev || >= 3.6.0-dev || >= 3.6.0-beta || >= 3.7.0-dev || >= 3.7.0-beta' - dependencies: - tslib: 1.14.1 - dev: true - /tsutils/3.21.0_typescript@2.9.2: resolution: {integrity: sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==} engines: {node: '>= 6'} From 4eb2ee8e2e89c930dcd6dc94f11b2bae3dd21c83 Mon Sep 17 00:00:00 2001 From: Sujith Date: Mon, 17 Oct 2022 23:35:48 +0530 Subject: [PATCH 02/33] feat: initial commit fulfillment base Signed-off-by: Sujith --- packages/api-plugin-fulfillment/.gitignore | 61 +++ packages/api-plugin-fulfillment/LICENSE | 201 +++++++++ packages/api-plugin-fulfillment/README.md | 68 +++ .../api-plugin-fulfillment/babel.config.cjs | 1 + packages/api-plugin-fulfillment/index.js | 3 + .../api-plugin-fulfillment/jest.config.cjs | 1 + packages/api-plugin-fulfillment/package.json | 49 +++ packages/api-plugin-fulfillment/src/index.js | 59 +++ .../createFulfillmentMethod.test.js.snap | 3 + .../src/mutations/createFulfillmentMethod.js | 59 +++ .../mutations/createFulfillmentMethod.test.js | 72 +++ .../src/mutations/createFulfillmentType.js | 38 ++ .../src/mutations/index.js | 15 + .../selectFulfillmentOptionForGroup.js | 57 +++ .../src/mutations/updateFulfillmentMethod.js | 69 +++ .../updateFulfillmentOptionsForGroup.js | 102 +++++ .../src/mutations/updateFulfillmentType.js | 55 +++ .../api-plugin-fulfillment/src/preStartup.js | 21 + .../src/queries/getFulfillmentMethods.js | 47 ++ .../getFulfillmentMethodsWithQuotes.js | 50 +++ .../src/queries/getFulfillmentType.js | 29 ++ .../src/queries/getFulfillmentTypes.js | 21 + .../src/queries/index.js | 12 + .../src/registration.js | 27 ++ .../src/resolvers/FulfillmentMethod/index.js | 5 + .../src/resolvers/Mutation/index.js | 11 + .../selectFulfillmentOptionForGroup.js | 43 ++ .../Mutation/updateFulfillmentMethod.js | 35 ++ .../updateFulfillmentOptionsForGroup.js | 32 ++ .../Mutation/updateFulfillmentType.js | 34 ++ .../resolvers/Query/getFulfillmentMethods.js | 32 ++ .../src/resolvers/Query/getFulfillmentType.js | 28 ++ .../resolvers/Query/getFulfillmentTypes.js | 31 ++ .../src/resolvers/Query/index.js | 9 + .../src/resolvers/index.js | 19 + .../src/schemas/index.js | 5 + .../src/schemas/schema.graphql | 414 ++++++++++++++++++ .../src/simpleSchemas.js | 133 ++++++ .../api-plugin-fulfillment/src/startup.js | 30 ++ .../src/tests/factory.js | 20 + .../src/util/extendCommonOrder.js | 93 ++++ .../src/util/getCartById.js | 34 ++ .../api-plugin-fulfillment/src/xforms/id.js | 18 + pnpm-lock.yaml | 185 +++++++- 44 files changed, 2327 insertions(+), 4 deletions(-) create mode 100644 packages/api-plugin-fulfillment/.gitignore create mode 100644 packages/api-plugin-fulfillment/LICENSE create mode 100644 packages/api-plugin-fulfillment/README.md create mode 100644 packages/api-plugin-fulfillment/babel.config.cjs create mode 100644 packages/api-plugin-fulfillment/index.js create mode 100644 packages/api-plugin-fulfillment/jest.config.cjs create mode 100644 packages/api-plugin-fulfillment/package.json create mode 100644 packages/api-plugin-fulfillment/src/index.js create mode 100644 packages/api-plugin-fulfillment/src/mutations/__snapshots__/createFulfillmentMethod.test.js.snap create mode 100644 packages/api-plugin-fulfillment/src/mutations/createFulfillmentMethod.js create mode 100644 packages/api-plugin-fulfillment/src/mutations/createFulfillmentMethod.test.js create mode 100644 packages/api-plugin-fulfillment/src/mutations/createFulfillmentType.js create mode 100644 packages/api-plugin-fulfillment/src/mutations/index.js create mode 100644 packages/api-plugin-fulfillment/src/mutations/selectFulfillmentOptionForGroup.js create mode 100644 packages/api-plugin-fulfillment/src/mutations/updateFulfillmentMethod.js create mode 100644 packages/api-plugin-fulfillment/src/mutations/updateFulfillmentOptionsForGroup.js create mode 100644 packages/api-plugin-fulfillment/src/mutations/updateFulfillmentType.js create mode 100644 packages/api-plugin-fulfillment/src/preStartup.js create mode 100644 packages/api-plugin-fulfillment/src/queries/getFulfillmentMethods.js create mode 100644 packages/api-plugin-fulfillment/src/queries/getFulfillmentMethodsWithQuotes.js create mode 100644 packages/api-plugin-fulfillment/src/queries/getFulfillmentType.js create mode 100644 packages/api-plugin-fulfillment/src/queries/getFulfillmentTypes.js create mode 100644 packages/api-plugin-fulfillment/src/queries/index.js create mode 100644 packages/api-plugin-fulfillment/src/registration.js create mode 100644 packages/api-plugin-fulfillment/src/resolvers/FulfillmentMethod/index.js create mode 100644 packages/api-plugin-fulfillment/src/resolvers/Mutation/index.js create mode 100644 packages/api-plugin-fulfillment/src/resolvers/Mutation/selectFulfillmentOptionForGroup.js create mode 100644 packages/api-plugin-fulfillment/src/resolvers/Mutation/updateFulfillmentMethod.js create mode 100644 packages/api-plugin-fulfillment/src/resolvers/Mutation/updateFulfillmentOptionsForGroup.js create mode 100644 packages/api-plugin-fulfillment/src/resolvers/Mutation/updateFulfillmentType.js create mode 100644 packages/api-plugin-fulfillment/src/resolvers/Query/getFulfillmentMethods.js create mode 100644 packages/api-plugin-fulfillment/src/resolvers/Query/getFulfillmentType.js create mode 100644 packages/api-plugin-fulfillment/src/resolvers/Query/getFulfillmentTypes.js create mode 100644 packages/api-plugin-fulfillment/src/resolvers/Query/index.js create mode 100644 packages/api-plugin-fulfillment/src/resolvers/index.js create mode 100644 packages/api-plugin-fulfillment/src/schemas/index.js create mode 100644 packages/api-plugin-fulfillment/src/schemas/schema.graphql create mode 100644 packages/api-plugin-fulfillment/src/simpleSchemas.js create mode 100644 packages/api-plugin-fulfillment/src/startup.js create mode 100644 packages/api-plugin-fulfillment/src/tests/factory.js create mode 100644 packages/api-plugin-fulfillment/src/util/extendCommonOrder.js create mode 100644 packages/api-plugin-fulfillment/src/util/getCartById.js create mode 100644 packages/api-plugin-fulfillment/src/xforms/id.js diff --git a/packages/api-plugin-fulfillment/.gitignore b/packages/api-plugin-fulfillment/.gitignore new file mode 100644 index 00000000000..ad46b30886f --- /dev/null +++ b/packages/api-plugin-fulfillment/.gitignore @@ -0,0 +1,61 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* + +# Runtime data +pids +*.pid +*.seed +*.pid.lock + +# Directory for instrumented libs generated by jscoverage/JSCover +lib-cov + +# Coverage directory used by tools like istanbul +coverage + +# nyc test coverage +.nyc_output + +# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) +.grunt + +# Bower dependency directory (https://bower.io/) +bower_components + +# node-waf configuration +.lock-wscript + +# Compiled binary addons (https://nodejs.org/api/addons.html) +build/Release + +# Dependency directories +node_modules/ +jspm_packages/ + +# TypeScript v1 declaration files +typings/ + +# Optional npm cache directory +.npm + +# Optional eslint cache +.eslintcache + +# Optional REPL history +.node_repl_history + +# Output of 'npm pack' +*.tgz + +# Yarn Integrity file +.yarn-integrity + +# dotenv environment variables file +.env + +# next.js build output +.next diff --git a/packages/api-plugin-fulfillment/LICENSE b/packages/api-plugin-fulfillment/LICENSE new file mode 100644 index 00000000000..261eeb9e9f8 --- /dev/null +++ b/packages/api-plugin-fulfillment/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/packages/api-plugin-fulfillment/README.md b/packages/api-plugin-fulfillment/README.md new file mode 100644 index 00000000000..53aaf9acead --- /dev/null +++ b/packages/api-plugin-fulfillment/README.md @@ -0,0 +1,68 @@ +# api-plugin-fulfillment + +[![npm (scoped)](https://img.shields.io/npm/v/@reactioncommerce/api-plugin-fulfillment.svg)](https://www.npmjs.com/package/@reactioncommerce/api-plugin-fulfillment) +[![CircleCI](https://circleci.com/gh/reactioncommerce/api-plugin-fulfillment.svg?style=svg)](https://circleci.com/gh/reactioncommerce/api-plugin-fulfillment) + + +## Summary + +This plugin provides the base for all fulfillment types and the fulfillment methods under each type. + +## Included in this fulfillment plugin +### `src/` + +The `src` folder is where all the plugin files resides. + +### `.gitignore` + +A basic `gitignore` file +### `babel.config.cjs` + +If your plugin includes linting and tests, this file is required to allow esmodules to run correctly. + +### `jest.config.cjs` + +If your plugin includes tests, this file is required to allow esmodules to run correctly. You'll need to update the `transformIgnorePatterns` and `moduleNameMapper` sections to include any esmodule `npm` packages used in your plugin. + +### `License.md` + +If your plugin uses `Apache 2` licensing, you can leave this file as-is. If another type of licensing is used, you need to update this file, and the README, accordingly. + +### `package.json` + +The provided `package.json` is set up to install all needed packages and config for linting, testing, and semantic-release. You'll need to update the `name`, `description`, and add any new dependencies your plugin files use. + +### `index.js` + +The entrypoint file for your npm package, will most likely just export your plugin registration from the `src` folder. + +## Developer Certificate of Origin +We use the [Developer Certificate of Origin (DCO)](https://developercertificate.org/) in lieu of a Contributor License Agreement for all contributions to Reaction Commerce open source projects. We request that contributors agree to the terms of the DCO and indicate that agreement by signing all commits made to Reaction Commerce projects by adding a line with your name and email address to every Git commit message contributed: +``` +Signed-off-by: Jane Doe +``` + +You can sign your commit automatically with Git by using `git commit -s` if you have your `user.name` and `user.email` set as part of your Git configuration. + +We ask that you use your real name (please no anonymous contributions or pseudonyms). By signing your commit you are certifying that you have the right have the right to submit it under the open source license used by that particular Reaction Commerce project. You must use your real name (no pseudonyms or anonymous contributions are allowed.) + +We use the [Probot DCO GitHub app](https://github.com/apps/dco) to check for DCO signoffs of every commit. + +If you forget to sign your commits, the DCO bot will remind you and give you detailed instructions for how to amend your commits to add a signature. + +## License + + Copyright 2020 Reaction Commerce + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + diff --git a/packages/api-plugin-fulfillment/babel.config.cjs b/packages/api-plugin-fulfillment/babel.config.cjs new file mode 100644 index 00000000000..5fa924c0809 --- /dev/null +++ b/packages/api-plugin-fulfillment/babel.config.cjs @@ -0,0 +1 @@ +module.exports = require("@reactioncommerce/api-utils/lib/configs/babel.config.cjs"); diff --git a/packages/api-plugin-fulfillment/index.js b/packages/api-plugin-fulfillment/index.js new file mode 100644 index 00000000000..d7ea8b28c59 --- /dev/null +++ b/packages/api-plugin-fulfillment/index.js @@ -0,0 +1,3 @@ +import register from "./src/index.js"; + +export default register; diff --git a/packages/api-plugin-fulfillment/jest.config.cjs b/packages/api-plugin-fulfillment/jest.config.cjs new file mode 100644 index 00000000000..2bdefefceb9 --- /dev/null +++ b/packages/api-plugin-fulfillment/jest.config.cjs @@ -0,0 +1 @@ +module.exports = require("@reactioncommerce/api-utils/lib/configs/jest.config.cjs"); diff --git a/packages/api-plugin-fulfillment/package.json b/packages/api-plugin-fulfillment/package.json new file mode 100644 index 00000000000..5497612a419 --- /dev/null +++ b/packages/api-plugin-fulfillment/package.json @@ -0,0 +1,49 @@ +{ + "name": "@reactioncommerce/api-plugin-fulfillment", + "description": "Base Fulfillment plugin for implementing other fulfillment type plugins and their corresponding fulfillment method plugins", + "label": "Fulfillment Plugin", + "version": "1.0.0", + "main": "index.js", + "type": "module", + "engines": { + "node": ">=14.18.1", + "npm": ">=7" + }, + "homepage": "https://github.com/reactioncommerce/reaction", + "url": "https://github.com/reactioncommerce/reaction.git", + "email": "hello-open-commerce@mailchimp.com", + "repository": { + "type": "git", + "url": "https://github.com/reactioncommerce/reaction.git", + "directory": "packages/api-plugin-fulfillment" + }, + "author": { + "name": "Mailchimp Open Commerce", + "email": "hello-open-commerce@mailchimp.com", + "url": "https://mailchimp.com/developer/open-commerce/" + }, + "license": "Apache-2.0", + "bugs": { + "url": "https://github.com/reactioncommerce/api-plugin-fulfillment/issues" + }, + "sideEffects": false, + "dependencies": { + "@reactioncommerce/logger": "^1.1.3", + "@reactioncommerce/api-utils": "^1.16.9", + "@reactioncommerce/reaction-error": "^1.0.1", + "@reactioncommerce/random": "^1.0.2", + "lodash": "^4.17.21", + "simpl-schema": "^1.12.2" + }, + "devDependencies": { + "@reactioncommerce/api-plugin-carts": "^1.0.0", + "@reactioncommerce/data-factory": "~1.0.1" + }, + "scripts": { + "lint": "npm run lint:eslint", + "lint:eslint": "eslint .", + "test": "jest", + "test:watch": "jest --watch", + "test:file": "jest --no-cache --watch --coverage=false" + } +} diff --git a/packages/api-plugin-fulfillment/src/index.js b/packages/api-plugin-fulfillment/src/index.js new file mode 100644 index 00000000000..2a006b76fac --- /dev/null +++ b/packages/api-plugin-fulfillment/src/index.js @@ -0,0 +1,59 @@ +import { createRequire } from "module"; +import mutations from "./mutations/index.js"; +import queries from "./queries/index.js"; +import resolvers from "./resolvers/index.js"; +import schemas from "./schemas/index.js"; +import startup from "./startup.js"; +import preStartup from "./preStartup.js"; +import { MethodEmptyData } from "./simpleSchemas.js"; +import { allRegisteredFulfillmentTypes, registerPluginHandlerForFulfillmentTypes } from "./registration.js"; + +const require = createRequire(import.meta.url); +const pkg = require("../package.json"); + +/** + * @summary Import and call this function to add this plugin to your API. + * @param {Object} app The ReactionAPI instance + * @returns {undefined} + */ +export default async function register(app) { + await app.registerPlugin({ + label: "Fulfillment", + name: "fulfillment", + version: pkg.version, + collections: { + FulfillmentRestrictions: { + name: "FulfillmentRestrictions", + indexes: [ + [{ methodIds: 1 }] + ] + }, + Fulfillment: { + name: "Fulfillment", + indexes: [ + // Create indexes. We set specific names for backwards compatibility + // with indexes created by the aldeed:schema-index Meteor package. + [{ name: 1 }, { name: "c2_name" }], + [{ shopId: 1 }, { name: "c2_shopId" }] + ] + } + }, + graphQL: { + schemas, + resolvers + }, + queries, + mutations, + functionsByType: { + registerPluginHandler: [registerPluginHandlerForFulfillmentTypes], + preStartup: [preStartup], + startup: [startup] + }, + contextAdditions: { + allRegisteredFulfillmentTypes + }, + simpleSchemas: { + MethodEmptyData + } + }); +} diff --git a/packages/api-plugin-fulfillment/src/mutations/__snapshots__/createFulfillmentMethod.test.js.snap b/packages/api-plugin-fulfillment/src/mutations/__snapshots__/createFulfillmentMethod.test.js.snap new file mode 100644 index 00000000000..79e2bc2c52b --- /dev/null +++ b/packages/api-plugin-fulfillment/src/mutations/__snapshots__/createFulfillmentMethod.test.js.snap @@ -0,0 +1,3 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`throws if required fields are not supplied 1`] = `"Fulfillment types is required"`; diff --git a/packages/api-plugin-fulfillment/src/mutations/createFulfillmentMethod.js b/packages/api-plugin-fulfillment/src/mutations/createFulfillmentMethod.js new file mode 100644 index 00000000000..b0985d03a74 --- /dev/null +++ b/packages/api-plugin-fulfillment/src/mutations/createFulfillmentMethod.js @@ -0,0 +1,59 @@ +import SimpleSchema from "simpl-schema"; +import Random from "@reactioncommerce/random"; +import ReactionError from "@reactioncommerce/reaction-error"; +import { FulfillmentMethodSchema } from "../simpleSchemas.js"; + +const inputSchema = new SimpleSchema({ + method: FulfillmentMethodSchema, + fulfillmentTypeId: String, + shopId: String +}); + +/** + * @method createFulfillmentMethodMutation + * @summary Creates a fulfillment method + * @param {Object} context - an object containing the per-request state + * @param {Object} input - Input (see SimpleSchema) + * @returns {Promise} An object with a `method` property containing the created method + */ +export default async function createFulfillmentMethodMutation(context, input) { + const cleanedInput = inputSchema.clean(input); // add default values and such + inputSchema.validate(cleanedInput); + + const { method: inputMethod, fulfillmentTypeId, shopId } = cleanedInput; + const { collections } = context; + const { Fulfillment } = collections; + const method = { ...inputMethod }; + + await context.validatePermissions("reaction:legacy:fulfillmentMethods", "create", { shopId }); + + const ffTypeRecord = await Fulfillment.findOne({ _id: fulfillmentTypeId, shopId }); + if (!ffTypeRecord) throw new ReactionError("server-error", "Unable to create fulfillment method without defined type"); + + const ffTypeMethodRecord = await Fulfillment.findOne({ + _id: fulfillmentTypeId, + shopId, + methods: { $elemMatch: { name: method.name, fulfillmentMethod: method.fulfillmentMethod } } + }); + if (ffTypeMethodRecord) throw new ReactionError("server-error", "Fulfillment Method already exists"); + + method._id = Random.id(); + // MongoDB schema still uses `enabled` rather than `isEnabled` + // method.enabled = method.isEnabled; + // delete method.isEnabled; + + method.fulfillmentTypes = [ffTypeRecord.fulfillmentType]; + + const { matchedCount } = await Fulfillment.updateOne({ + shopId, + _id: fulfillmentTypeId + }, { + $addToSet: { + methods: method + } + }); + + if (matchedCount === 0) throw new ReactionError("server-error", "Unable to create fulfillment method"); + + return { group: method }; +} diff --git a/packages/api-plugin-fulfillment/src/mutations/createFulfillmentMethod.test.js b/packages/api-plugin-fulfillment/src/mutations/createFulfillmentMethod.test.js new file mode 100644 index 00000000000..9248b986a28 --- /dev/null +++ b/packages/api-plugin-fulfillment/src/mutations/createFulfillmentMethod.test.js @@ -0,0 +1,72 @@ +import mockCollection from "@reactioncommerce/api-utils/tests/mockCollection.js"; +import mockContext from "@reactioncommerce/api-utils/tests/mockContext.js"; +import createFulfillmentMethodMutation from "./createFulfillmentMethod.js"; + +mockContext.validatePermissions = jest.fn().mockName("validatePermissions"); +mockContext.collections.Fulfillment = mockCollection("Fulfillment"); +mockContext.validatePermissions.mockReturnValueOnce(Promise.resolve(null)); + +test("throws if required fields are not supplied", async () => { + const fulfillmentMethodInput = { + method: { + shopId: "SHOP_ID", + cost: 99, + fulfillmentMethod: "flatRates", + displayMessageMethod: "Sample display message" + }, + fulfillmentTypeId: "fulfillment123", + shopId: "SHOP_ID" + }; + + await expect(createFulfillmentMethodMutation(mockContext, fulfillmentMethodInput)).rejects.toThrowErrorMatchingSnapshot(); +}); + +test("add a new fulfillment method", async () => { + mockContext.collections.Fulfillment.updateOne.mockReturnValueOnce(Promise.resolve({ result: { ok: 1 } })); + mockContext.collections.Fulfillment.findOne.mockReturnValueOnce(Promise.resolve({ + _id: "fulfillment123", + name: "Default Shipping Provider", + shopId: "SHOP_ID", + provider: { + enabled: true, + label: "Shipping", + name: "shipping" + }, + fulfillmentType: "shipping" + })); + + + const result = await createFulfillmentMethodMutation(mockContext, { + method: { + shopId: "SHOP_ID", + cost: 99, + handling: 99, + rate: 99, + fulfillmentTypes: ["shipping"], + group: "Ground", + enabled: true, + label: "Flat Rate", + name: "flatRates", + fulfillmentMethod: "flatRates", + displayMessageMethod: "Sample display message" + }, + fulfillmentTypeId: "fulfillment123", + shopId: "SHOP_ID" + }); + + expect(result).toEqual({ + group: { + _id: expect.any(String), + cost: 99, + handling: 99, + rate: 99, + fulfillmentTypes: ["shipping"], + group: "Ground", + enabled: true, + label: "Flat Rate", + name: "flatRates", + fulfillmentMethod: "flatRates", + displayMessageMethod: "Sample display message" + } + }); +}); diff --git a/packages/api-plugin-fulfillment/src/mutations/createFulfillmentType.js b/packages/api-plugin-fulfillment/src/mutations/createFulfillmentType.js new file mode 100644 index 00000000000..bd2bcadfcfa --- /dev/null +++ b/packages/api-plugin-fulfillment/src/mutations/createFulfillmentType.js @@ -0,0 +1,38 @@ +import Random from "@reactioncommerce/random"; +import ReactionError from "@reactioncommerce/reaction-error"; +import { fulfillmentTypeSchema } from "../simpleSchemas.js"; + +/** + * @method createFulfillmentType + * @summary updates the selected fulfillment type + * @param {Object} context - an object containing the per-request state + * @param {Object} input - Input (see SimpleSchema) + * @returns {Promise} An object with the updated type + */ +export default async function createFulfillmentType(context, input) { + const cleanedInput = fulfillmentTypeSchema.clean(input); + cleanedInput.provider.name = cleanedInput.name; + + if (cleanedInput.method) { + cleanedInput.method._id = Random.id(); + cleanedInput.method.fulfillmentTypes = [cleanedInput.fulfillmentType]; + } + fulfillmentTypeSchema.validate(cleanedInput); + + const { collections } = context; + const { Fulfillment } = collections; + const { shopId, name, fulfillmentType } = cleanedInput; + + const ffTypeRecord = await Fulfillment.findOne({ name, shopId, fulfillmentType }); + if (ffTypeRecord) throw new ReactionError("invalid-parameter", "Fulfillment Type already exists"); + + await context.validatePermissions("reaction:legacy:fulfillmentTypes", "create", { shopId }); + + const { insertedCount } = await Fulfillment.insertOne({ + _id: Random.id(), + ...cleanedInput + }); + if (insertedCount === 0) throw new ReactionError("server-error", "Unable to create fulfillment type"); + + return { group: { name: cleanedInput.name, fulfillmentType: cleanedInput.fulfillmentType } }; +} diff --git a/packages/api-plugin-fulfillment/src/mutations/index.js b/packages/api-plugin-fulfillment/src/mutations/index.js new file mode 100644 index 00000000000..53b79e27d40 --- /dev/null +++ b/packages/api-plugin-fulfillment/src/mutations/index.js @@ -0,0 +1,15 @@ +import selectFulfillmentOptionForGroup from "./selectFulfillmentOptionForGroup.js"; +import updateFulfillmentOptionsForGroup from "./updateFulfillmentOptionsForGroup.js"; +import updateFulfillmentType from "./updateFulfillmentType.js"; +import createFulfillmentType from "./createFulfillmentType.js"; +import updateFulfillmentMethod from "./updateFulfillmentMethod.js"; +import createFulfillmentMethod from "./createFulfillmentMethod.js"; + +export default { + createFulfillmentType, + updateFulfillmentType, + createFulfillmentMethod, + updateFulfillmentMethod, + selectFulfillmentOptionForGroup, + updateFulfillmentOptionsForGroup +}; diff --git a/packages/api-plugin-fulfillment/src/mutations/selectFulfillmentOptionForGroup.js b/packages/api-plugin-fulfillment/src/mutations/selectFulfillmentOptionForGroup.js new file mode 100644 index 00000000000..f6a56024e21 --- /dev/null +++ b/packages/api-plugin-fulfillment/src/mutations/selectFulfillmentOptionForGroup.js @@ -0,0 +1,57 @@ +import SimpleSchema from "simpl-schema"; +import ReactionError from "@reactioncommerce/reaction-error"; +import getCartById from "../util/getCartById.js"; + +const inputSchema = new SimpleSchema({ + cartId: String, + cartToken: { + type: String, + optional: true + }, + fulfillmentGroupId: String, + fulfillmentMethodId: String +}); + +/** + * @method selectFulfillmentOptionForGroup + * @summary Selects a fulfillment option for a fulfillment group + * @param {Object} context - an object containing the per-request state + * @param {Object} input - an object of all mutation arguments that were sent by the client + * @param {String} input.cartId - The ID of the cart to select a fulfillment option for + * @param {String} [input.cartToken] - The token for the cart, required if it is an anonymous cart + * @param {String} input.fulfillmentGroupId - The group to select a fulfillment option for + * @param {String} input.fulfillmentMethodId - The fulfillment method ID from the option the shopper selected + * @returns {Promise} An object with a `cart` property containing the updated cart + */ +export default async function selectFulfillmentOptionForGroup(context, input) { + const cleanedInput = inputSchema.clean(input || {}); + + inputSchema.validate(cleanedInput); + + const { cartId, cartToken, fulfillmentGroupId, fulfillmentMethodId } = cleanedInput; + + const cart = await getCartById(context, cartId, { cartToken, throwIfNotFound: true }); + + const fulfillmentGroup = (cart.shipping || []).find((group) => group._id === fulfillmentGroupId); + if (!fulfillmentGroup) throw new ReactionError("not-found", `Fulfillment group with ID ${fulfillmentGroupId} not found in cart with ID ${cartId}`); + + // Make sure there is an option for this group that has the requested ID + const option = (fulfillmentGroup.shipmentQuotes || []).find((quote) => quote.method._id === fulfillmentMethodId); + if (!option) throw new ReactionError("not-found", `Fulfillment option with method ID ${fulfillmentMethodId} not found in cart with ID ${cartId}`); + + const updatedCart = { + ...cart, + shipping: cart.shipping.map((group) => { + if (group._id === fulfillmentGroupId) { + return { ...group, shipmentMethod: option.method }; + } + + return group; + }), + updatedAt: new Date() + }; + + const savedCart = await context.mutations.saveCart(context, updatedCart); + + return { cart: savedCart }; +} diff --git a/packages/api-plugin-fulfillment/src/mutations/updateFulfillmentMethod.js b/packages/api-plugin-fulfillment/src/mutations/updateFulfillmentMethod.js new file mode 100644 index 00000000000..152c7a215f1 --- /dev/null +++ b/packages/api-plugin-fulfillment/src/mutations/updateFulfillmentMethod.js @@ -0,0 +1,69 @@ +import SimpleSchema from "simpl-schema"; +import ReactionError from "@reactioncommerce/reaction-error"; +import { FulfillmentMethodSchema } from "../simpleSchemas.js"; + +const inputSchema = new SimpleSchema({ + method: FulfillmentMethodSchema, + fulfillmentTypeId: String, + methodId: String, + shopId: String +}); + +/** + * @method updateFulfillmentMethodMutation + * @summary updates a flat rate fulfillment method + * @param {Object} context - an object containing the per-request state + * @param {Object} input - Input (see SimpleSchema) + * @returns {Promise} An object with a `method` property containing the updated method + */ +export default async function updateFulfillmentMethodMutation(context, input) { + const cleanedInput = inputSchema.clean(input); // add default values and such + inputSchema.validate(cleanedInput); + + const { method: inputMethod, methodId, fulfillmentTypeId, shopId } = cleanedInput; + const { collections } = context; + const { Fulfillment } = collections; + const method = { ...inputMethod }; + + if (!fulfillmentTypeId) throw new ReactionError("invalid-parameter", "Fulfillment Type ID to be updated not provided"); + if (!methodId) throw new ReactionError("invalid-parameter", "Method ID to be updated not provided"); + + await context.validatePermissions(`reaction:legacy:fulfillmentMethods:${methodId}`, "update", { shopId }); + + // MongoDB schema still uses `enabled` rather than `isEnabled` + // method.enabled = method.isEnabled; + // delete method.isEnabled; + + const ffTypeMethodRecord = await Fulfillment.findOne({ + "_id": fulfillmentTypeId, + shopId, + "methods._id": methodId + }); + if (!ffTypeMethodRecord) throw new ReactionError("server-error", "Fulfillment Method does not exist"); + + // Do not update the fulfillmentType, fulfillmentMethod, group, name & _id fields + // Find the matching fulfillmentMethod object and use those values to over-write + const currentFulfillmentMethod = (ffTypeMethodRecord.methods || []).find((meth) => meth._id === methodId); + if (!currentFulfillmentMethod) throw new ReactionError("server-error", "Fulfillment Method does not exist"); + const updatedMethod = { + ...method, + _id: methodId, + name: currentFulfillmentMethod.name, + group: currentFulfillmentMethod.group, + fulfillmentMethod: currentFulfillmentMethod.fulfillmentMethod, + fulfillmentType: [ffTypeMethodRecord.fulfillmentType] + }; + + const { matchedCount } = await Fulfillment.updateOne({ + "_id": fulfillmentTypeId, + "methods._id": methodId, + shopId + }, { + $set: { + "methods.$": updatedMethod + } + }); + if (matchedCount === 0) throw new ReactionError("not-found", "Fulfillment type to be updated not found"); + + return { group: updatedMethod }; +} diff --git a/packages/api-plugin-fulfillment/src/mutations/updateFulfillmentOptionsForGroup.js b/packages/api-plugin-fulfillment/src/mutations/updateFulfillmentOptionsForGroup.js new file mode 100644 index 00000000000..ada20aa6637 --- /dev/null +++ b/packages/api-plugin-fulfillment/src/mutations/updateFulfillmentOptionsForGroup.js @@ -0,0 +1,102 @@ +import _ from "lodash"; +import SimpleSchema from "simpl-schema"; +import ReactionError from "@reactioncommerce/reaction-error"; +import getCartById from "../util/getCartById.js"; + +const inputSchema = new SimpleSchema({ + cartId: String, + cartToken: { + type: String, + optional: true + }, + fulfillmentGroupId: String +}); + +/** + * @name getShipmentQuotesQueryStatus + * @param {Array} rates Rate array + * @returns {Object} An object with `shipmentQuotes` and `shipmentQuotesQueryStatus` on it + * @private + */ +function getShipmentQuotesQueryStatus(rates) { + if (rates.length === 0) { + return { + shipmentQuotes: [], + shipmentQuotesQueryStatus: { + requestStatus: "pending" + } + }; + } + + const errorResult = rates.find((option) => option.requestStatus === "error"); + if (errorResult) { + return { + shipmentQuotes: [], + shipmentQuotesQueryStatus: { + requestStatus: errorResult.requestStatus, + shippingProvider: errorResult.shippingProvider, + message: errorResult.message + } + }; + } + + return { + shipmentQuotes: rates, + shipmentQuotesQueryStatus: { + requestStatus: "success", + numOfShippingMethodsFound: rates.length + } + }; +} + +/** + * @method updateFulfillmentOptionsForGroup + * @summary Updates the fulfillment quotes for a fulfillment group + * @param {Object} context - an object containing the per-request state + * @param {Object} input - an object of all mutation arguments that were sent by the client + * @param {String} input.cartId - The ID of the cart to update fulfillment options for + * @param {String} [input.cartToken] - The token for the cart, required if it is an anonymous cart + * @param {String} input.fulfillmentGroupId - The group to update fulfillment options for + * @returns {Promise} An object with a `cart` property containing the updated cart + */ +export default async function updateFulfillmentOptionsForGroup(context, input) { + const cleanedInput = inputSchema.clean(input || {}); + inputSchema.validate(cleanedInput); + + const { cartId, cartToken, fulfillmentGroupId } = cleanedInput; + + const cart = await getCartById(context, cartId, { cartToken, throwIfNotFound: true }); + + // This is done by `saveCart`, too, but we need to do it before every call to `getCommonOrderForCartGroup` + // to avoid errors in the case where a product has been deleted since the last time this cart was saved. + // This mutates that `cart` object. + await context.mutations.removeMissingItemsFromCart(context, cart); + + const fulfillmentGroup = (cart.shipping || []).find((group) => group._id === fulfillmentGroupId); + if (!fulfillmentGroup) throw new ReactionError("not-found", `Fulfillment group with ID ${fulfillmentGroupId} not found in cart with ID ${cartId}`); + + const commonOrder = await context.queries.getCommonOrderForCartGroup(context, { cart, fulfillmentGroupId: fulfillmentGroup._id }); + // In the future we want to do this async and subscribe to the results + const rates = await context.queries.getFulfillmentMethodsWithQuotes(commonOrder, context); + + const { shipmentQuotes, shipmentQuotesQueryStatus } = getShipmentQuotesQueryStatus(rates); + + if (!_.isEqual(shipmentQuotes, fulfillmentGroup.shipmentQuotes) || !_.isEqual(shipmentQuotesQueryStatus, fulfillmentGroup.shipmentQuotesQueryStatus)) { + const updatedCart = { + ...cart, + shipping: cart.shipping.map((group) => { + if (group._id === fulfillmentGroupId) { + return { ...group, shipmentQuotes, shipmentQuotesQueryStatus }; + } + + return group; + }), + updatedAt: new Date() + }; + + const savedCart = await context.mutations.saveCart(context, updatedCart); + + return { cart: savedCart }; + } + return { cart }; +} diff --git a/packages/api-plugin-fulfillment/src/mutations/updateFulfillmentType.js b/packages/api-plugin-fulfillment/src/mutations/updateFulfillmentType.js new file mode 100644 index 00000000000..089c91e0b4c --- /dev/null +++ b/packages/api-plugin-fulfillment/src/mutations/updateFulfillmentType.js @@ -0,0 +1,55 @@ +import SimpleSchema from "simpl-schema"; +import ReactionError from "@reactioncommerce/reaction-error"; + +const inputSchema = new SimpleSchema({ + fulfillmentGroupId: String, + shopId: String, + name: String, + enabled: { + type: Boolean, + defaultValue: true + }, + label: String, + displayMessageType: { + type: String, + optional: true + } +}); + +/** + * @method updateFulfillmentType + * @summary updates the selected fulfillment type + * @param {Object} context - an object containing the per-request state + * @param {Object} input - Input (see SimpleSchema) + * @returns {Promise} An object with the updated type + */ +export default async function updateFulfillmentType(context, input) { + const cleanedInput = inputSchema.clean(input); // add default values and such + inputSchema.validate(cleanedInput); + + const { fulfillmentGroupId, shopId, name, enabled, label, displayMessageType } = cleanedInput; + const { collections } = context; + const { Fulfillment } = collections; + + if (!shopId) throw new ReactionError("invalid-parameter", "Shop ID to be updated not provided"); + if (!fulfillmentGroupId) throw new ReactionError("invalid-parameter", "FulfillmentType ID to be updated not provided"); + if (!name) throw new ReactionError("invalid-parameter", "FulfillmentType Name to be updated not provided"); + + await context.validatePermissions(`reaction:legacy:fulfillmentTypes:${fulfillmentGroupId}`, "update", { shopId }); + + const { matchedCount } = await Fulfillment.updateOne({ + _id: fulfillmentGroupId, + shopId + }, { + $set: { + name, + "provider.enabled": enabled, + "provider.name": name, + "provider.label": label, + displayMessageType + } + }); + if (matchedCount === 0) throw new ReactionError("not-found", "Fulfillment type to update not found"); + + return { group: cleanedInput }; +} diff --git a/packages/api-plugin-fulfillment/src/preStartup.js b/packages/api-plugin-fulfillment/src/preStartup.js new file mode 100644 index 00000000000..a2f7d44ccf5 --- /dev/null +++ b/packages/api-plugin-fulfillment/src/preStartup.js @@ -0,0 +1,21 @@ +import Logger from "@reactioncommerce/logger"; +import ReactionError from "@reactioncommerce/reaction-error"; +import { extendFulfillmentSchemas } from "./simpleSchemas.js"; + +const logCtx = { name: "fulfillment", file: "preStartup" }; + +/** + * @summary Called before startup to extend schemas + * @param {Object} context Startup context + * @returns {undefined} + */ +export default async function fulfillmentPreStartup(context) { + const allFulfillmentTypesArray = context.allRegisteredFulfillmentTypes?.registeredFulfillmentTypes; + + if (!allFulfillmentTypesArray || allFulfillmentTypesArray.length === 0) { + Logger.warn(logCtx, "No fulfillment types available"); + throw new ReactionError("not-configured", "No fulfillment types available"); + } + + extendFulfillmentSchemas(context.simpleSchemas, allFulfillmentTypesArray); +} diff --git a/packages/api-plugin-fulfillment/src/queries/getFulfillmentMethods.js b/packages/api-plugin-fulfillment/src/queries/getFulfillmentMethods.js new file mode 100644 index 00000000000..3731ea54832 --- /dev/null +++ b/packages/api-plugin-fulfillment/src/queries/getFulfillmentMethods.js @@ -0,0 +1,47 @@ +/** + * @name getFulfillmentMethods + * @method + * @memberof Fulfillment/Queries + * @summary Query the Fulfillment collection for a list of fulfillment Methods + * @param {Object} context - an object containing the per-request state + * @param {Object} input - Request input + * @param {String} input.shopId - The shop id of the fulfillment types + * @param {String} input.fulfillmentTypeId - The fulfillmentType id of the fulfillment type + * @returns {Promise} Mongo cursor + */ +export default async function getFulfillmentMethods(context, input) { + const { collections } = context; + const { Fulfillment } = collections; + const { shopId, fulfillmentTypeId } = input; + + await context.validatePermissions("reaction:legacy:fulfillmentTypes", "read", { shopId }); + + // aggregate pipeline to extract fulfillment methods inside Fulfillment collection + return { + collection: Fulfillment, + pipeline: [ + { + $match: { + shopId, + _id: fulfillmentTypeId + } + }, + { + $unwind: "$methods" + }, + { + $replaceRoot: { + newRoot: { + $mergeObjects: [ + "$methods", + { + fulfillmentTypeId: "$$ROOT._id", + shopId: "$$ROOT.shopId" + } + ] + } + } + } + ] + }; +} diff --git a/packages/api-plugin-fulfillment/src/queries/getFulfillmentMethodsWithQuotes.js b/packages/api-plugin-fulfillment/src/queries/getFulfillmentMethodsWithQuotes.js new file mode 100644 index 00000000000..02b1428e6c2 --- /dev/null +++ b/packages/api-plugin-fulfillment/src/queries/getFulfillmentMethodsWithQuotes.js @@ -0,0 +1,50 @@ +import Logger from "@reactioncommerce/logger"; +import ReactionError from "@reactioncommerce/reaction-error"; +import extendCommonOrder from "../util/extendCommonOrder.js"; + +const logCtx = { name: "fulfillment", file: "getFulfillmentWithQuotes" }; + +/** + * @name getFulfillmentMethodsWithQuotes + * @method + * @summary Just gets rates, without updating anything + * @param {Object} commonOrder - details about the purchase a user wants to make. + * @param {Object} context - Context + * @returns {Array} return updated rates in cart + * @private + */ +export default async function getFulfillmentMethodsWithQuotes(commonOrder, context) { + const rates = []; + const retrialTargets = []; + + // must have items to calculate shipping + if (!commonOrder.items || !commonOrder.items.length) { + Logger.debug(logCtx, "getFulfillmentMethodsWithQuotes called with CommonOrder with no items"); + return rates; + } + const commonOrderExtended = await extendCommonOrder(context, commonOrder); + + const fulfillmentTypeInGroup = commonOrder.fulfillmentType; + if (!fulfillmentTypeInGroup) throw new ReactionError("not-found", "Fulfillment type not found in commonOrder"); + + const functionTypesToCall = `getFulfillmentMethodsWithQuotes${fulfillmentTypeInGroup}`; + const funcs = context.getFunctionsOfType(functionTypesToCall); + + if (!funcs || !Array.isArray(funcs) || !funcs.length) throw new ReactionError("not-found", `No methods for Fulfillment type ${fulfillmentTypeInGroup}`); + + let promises = funcs.map((rateFunction) => rateFunction(context, commonOrderExtended, [rates, retrialTargets])); + await Promise.all(promises); + + // Try once more. + if (retrialTargets.length > 0) { + promises = funcs.map((rateFunction) => rateFunction(context, commonOrderExtended, [rates, retrialTargets])); + await Promise.all(promises); + + if (retrialTargets.length > 0) { + Logger.warn({ ...logCtx, retrialTargets }, "Failed to get fulfillment methods from these packages:"); + } + } + + Logger.debug({ ...logCtx, rates }, "getFulfillmentMethodsWithQuotes returning rates"); + return rates; +} diff --git a/packages/api-plugin-fulfillment/src/queries/getFulfillmentType.js b/packages/api-plugin-fulfillment/src/queries/getFulfillmentType.js new file mode 100644 index 00000000000..c2b0596efc2 --- /dev/null +++ b/packages/api-plugin-fulfillment/src/queries/getFulfillmentType.js @@ -0,0 +1,29 @@ +/** + * @name getFulfillmentType + * @method + * @memberof Fulfillment/Queries + * @summary Query the Fulfillment collection for a single fulfillment type + * @param {Object} context - an object containing the per-request state + * @param {Object} input - Request input + * @param {String} input.fulfillmentTypeId - The fulfillment type id + * @param {String} input.shopId - The shop id of the fulfillment type + * @returns {Promise} Mongo cursor + */ +export default async function getFulfillmentType(context, input) { + const { collections } = context; + const { Fulfillment } = collections; + const { fulfillmentTypeId, shopId } = input; + + await context.validatePermissions("reaction:legacy:fulfillmentTypes", "read", { shopId }); + + const doc = await Fulfillment.findOne({ + _id: fulfillmentTypeId, + shopId + }); + if (!doc) return null; + + return { + ...doc, + shopId + }; +} diff --git a/packages/api-plugin-fulfillment/src/queries/getFulfillmentTypes.js b/packages/api-plugin-fulfillment/src/queries/getFulfillmentTypes.js new file mode 100644 index 00000000000..546d01d672f --- /dev/null +++ b/packages/api-plugin-fulfillment/src/queries/getFulfillmentTypes.js @@ -0,0 +1,21 @@ +/** + * @name getFulfillmentTypes + * @method + * @memberof Fulfillment/Queries + * @summary Query the Fulfillment collection for a list of fulfillment types + * @param {Object} context - an object containing the per-request state + * @param {Object} input - Request input + * @param {String} input.shopId - The shop id of the fulfillment types + * @returns {Promise} Mongo cursor + */ +export default async function getFulfillmentTypes(context, input) { + const { collections } = context; + const { Fulfillment } = collections; + const { shopId } = input; + + await context.validatePermissions("reaction:legacy:fulfillmentTypes", "read", { shopId }); + + return Fulfillment.find({ + shopId + }); +} diff --git a/packages/api-plugin-fulfillment/src/queries/index.js b/packages/api-plugin-fulfillment/src/queries/index.js new file mode 100644 index 00000000000..3582994cb92 --- /dev/null +++ b/packages/api-plugin-fulfillment/src/queries/index.js @@ -0,0 +1,12 @@ +// import allFulfillmentTypes from "./allFulfillmentTypes.js"; +import getFulfillmentMethodsWithQuotes from "./getFulfillmentMethodsWithQuotes.js"; +import getFulfillmentType from "./getFulfillmentType.js"; +import getFulfillmentTypes from "./getFulfillmentTypes.js"; +import getFulfillmentMethods from "./getFulfillmentMethods.js"; + +export default { + getFulfillmentType, + getFulfillmentTypes, + getFulfillmentMethods, + getFulfillmentMethodsWithQuotes +}; diff --git a/packages/api-plugin-fulfillment/src/registration.js b/packages/api-plugin-fulfillment/src/registration.js new file mode 100644 index 00000000000..9645f27e9eb --- /dev/null +++ b/packages/api-plugin-fulfillment/src/registration.js @@ -0,0 +1,27 @@ +import SimpleSchema from "simpl-schema"; + +const FulfillmentTypeDeclaration = new SimpleSchema({ + "registeredFulfillmentTypes": { + type: Array + }, + "registeredFulfillmentTypes.$": { + type: String + } +}); + +export const allRegisteredFulfillmentTypes = { + registeredFulfillmentTypes: ["undecided"] +}; + +/** + * @summary Collect all the registered Fulfillment types + * @param {Object} registeredFulfillmentTypes - Fulfillment types passed in via child plugins + * @returns {undefined} undefined + */ +export function registerPluginHandlerForFulfillmentTypes({ registeredFulfillmentTypes }) { + if (registeredFulfillmentTypes) { + allRegisteredFulfillmentTypes.registeredFulfillmentTypes = allRegisteredFulfillmentTypes.registeredFulfillmentTypes.concat(registeredFulfillmentTypes); + } + + FulfillmentTypeDeclaration.validate(allRegisteredFulfillmentTypes); +} diff --git a/packages/api-plugin-fulfillment/src/resolvers/FulfillmentMethod/index.js b/packages/api-plugin-fulfillment/src/resolvers/FulfillmentMethod/index.js new file mode 100644 index 00000000000..1a84788268b --- /dev/null +++ b/packages/api-plugin-fulfillment/src/resolvers/FulfillmentMethod/index.js @@ -0,0 +1,5 @@ +import { encodeFulfillmentMethodOpaqueId } from "../../xforms/id.js"; + +export default { + _id: (node) => encodeFulfillmentMethodOpaqueId(node._id) +}; diff --git a/packages/api-plugin-fulfillment/src/resolvers/Mutation/index.js b/packages/api-plugin-fulfillment/src/resolvers/Mutation/index.js new file mode 100644 index 00000000000..6ae592f0426 --- /dev/null +++ b/packages/api-plugin-fulfillment/src/resolvers/Mutation/index.js @@ -0,0 +1,11 @@ +import selectFulfillmentOptionForGroup from "./selectFulfillmentOptionForGroup.js"; +import updateFulfillmentOptionsForGroup from "./updateFulfillmentOptionsForGroup.js"; +import updateFulfillmentType from "./updateFulfillmentType.js"; +import updateFulfillmentMethod from "./updateFulfillmentMethod.js"; + +export default { + updateFulfillmentMethod, + updateFulfillmentType, + selectFulfillmentOptionForGroup, + updateFulfillmentOptionsForGroup +}; diff --git a/packages/api-plugin-fulfillment/src/resolvers/Mutation/selectFulfillmentOptionForGroup.js b/packages/api-plugin-fulfillment/src/resolvers/Mutation/selectFulfillmentOptionForGroup.js new file mode 100644 index 00000000000..260efda3b50 --- /dev/null +++ b/packages/api-plugin-fulfillment/src/resolvers/Mutation/selectFulfillmentOptionForGroup.js @@ -0,0 +1,43 @@ +import { decodeCartOpaqueId, decodeFulfillmentGroupOpaqueId, decodeFulfillmentMethodOpaqueId } from "../../xforms/id.js"; +import selectFulfillmentOptionForGroupMutation from "../../mutations/selectFulfillmentOptionForGroup.js"; + +/** + * @name Mutation/selectFulfillmentOptionForGroup + * @method + * @memberof Cart/GraphQL + * @summary resolver for the selectFulfillmentOptionForGroup GraphQL mutation + * @param {Object} parentResult - unused + * @param {Object} args.input - an object of all mutation arguments that were sent by the client + * @param {String} args.input.cartId - The ID of the cart to select a fulfillment option for + * @param {String} [args.input.cartToken] - The token for the cart, required if it is an anonymous cart + * @param {String} args.input.fulfillmentGroupId - The group to select a fulfillment option for + * @param {String} args.input.fulfillmentMethodId - The fulfillment method ID from the option the shopper selected + * @param {String} [args.input.clientMutationId] - An optional string identifying the mutation call + * @param {Object} context - an object containing the per-request state + * @returns {Promise} SelectFulfillmentOptionForGroupPayload + */ +export default async function selectFulfillmentOptionForGroup(parentResult, { input }, context) { + const { + cartId: opaqueCartId, + cartToken, + clientMutationId = null, + fulfillmentGroupId: opaqueFulfillmentGroupId, + fulfillmentMethodId: opaqueFulfillmentMethodId + } = input; + + const cartId = decodeCartOpaqueId(opaqueCartId); + const fulfillmentGroupId = decodeFulfillmentGroupOpaqueId(opaqueFulfillmentGroupId); + const fulfillmentMethodId = decodeFulfillmentMethodOpaqueId(opaqueFulfillmentMethodId); + + const { cart } = await selectFulfillmentOptionForGroupMutation(context, { + cartId, + cartToken, + fulfillmentGroupId, + fulfillmentMethodId + }); + + return { + cart, + clientMutationId + }; +} diff --git a/packages/api-plugin-fulfillment/src/resolvers/Mutation/updateFulfillmentMethod.js b/packages/api-plugin-fulfillment/src/resolvers/Mutation/updateFulfillmentMethod.js new file mode 100644 index 00000000000..d99e72074f0 --- /dev/null +++ b/packages/api-plugin-fulfillment/src/resolvers/Mutation/updateFulfillmentMethod.js @@ -0,0 +1,35 @@ +import { decodeShopOpaqueId, decodeFulfillmentGroupOpaqueId, decodeFulfillmentMethodOpaqueId } from "../../xforms/id.js"; +import updateFulfillmentMethodMutation from "../../mutations/updateFulfillmentMethod.js"; +/** + * @name Mutation/updateFulfillmentMethod + * @method + * @memberof Cart/GraphQL + * @summary resolver for the updateFulfillmentMethod GraphQL mutation + * @param {Object} parentResult - unused + * @param {Object} args.input - an object of all mutation arguments that were sent by the client + * @param {String} args.input.fulfillmentTypeId - The fulfillment type to be updated + * @param {String} args.input.methodId - The fulfillment method to be updated + * @param {String} args.input.shopId - The ShopId to which the fulfillment group belongs + * @param {String} args.input.method - The fulfillment method data to be updated + * @param {String} [args.input.clientMutationId] - An optional string identifying the mutation call + * @param {Object} context - an object containing the per-request state + * @returns {Promise} updateFulfillmentMethodPayload + */ +export default async function updateFulfillmentMethod(parentResult, { input }, context) { + const { shopId: opaqueShopId, clientMutationId = null, fulfillmentTypeId: opaqueFulfillmentTypeId, methodId: opaqueMethodId, method } = input.groupInfo; + + const methodId = decodeFulfillmentMethodOpaqueId(opaqueMethodId); + const fulfillmentTypeId = decodeFulfillmentGroupOpaqueId(opaqueFulfillmentTypeId); + const shopId = decodeShopOpaqueId(opaqueShopId); + const { group } = await updateFulfillmentMethodMutation(context, { + shopId, + fulfillmentTypeId, + methodId, + method + }); + + return { + group, + clientMutationId + }; +} diff --git a/packages/api-plugin-fulfillment/src/resolvers/Mutation/updateFulfillmentOptionsForGroup.js b/packages/api-plugin-fulfillment/src/resolvers/Mutation/updateFulfillmentOptionsForGroup.js new file mode 100644 index 00000000000..0045b0dee80 --- /dev/null +++ b/packages/api-plugin-fulfillment/src/resolvers/Mutation/updateFulfillmentOptionsForGroup.js @@ -0,0 +1,32 @@ +import { decodeCartOpaqueId, decodeFulfillmentGroupOpaqueId } from "../../xforms/id.js"; +import updateFulfillmentOptionsForGroupMutation from "../../mutations/updateFulfillmentOptionsForGroup.js"; +/** + * @name Mutation/updateFulfillmentOptionsForGroup + * @method + * @memberof Cart/GraphQL + * @summary resolver for the updateFulfillmentOptionsForGroup GraphQL mutation + * @param {Object} parentResult - unused + * @param {Object} args.input - an object of all mutation arguments that were sent by the client + * @param {String} args.input.cartId - The ID of the cart to update fulfillment options for + * @param {String} [args.input.cartToken] - The token for the cart, required if it is an anonymous cart + * @param {String} args.input.fulfillmentGroupId - The group to update fulfillment options for + * @param {String} [args.input.clientMutationId] - An optional string identifying the mutation call + * @param {Object} context - an object containing the per-request state + * @returns {Promise} UpdateFulfillmentOptionsForGroupPayload + */ +export default async function updateFulfillmentOptionsForGroup(parentResult, { input }, context) { + const { cartId: opaqueCartId, cartToken, clientMutationId = null, fulfillmentGroupId: opaqueFulfillmentGroupId } = input; + + const fulfillmentGroupId = decodeFulfillmentGroupOpaqueId(opaqueFulfillmentGroupId); + const cartId = decodeCartOpaqueId(opaqueCartId); + const { cart } = await updateFulfillmentOptionsForGroupMutation(context, { + cartId, + cartToken, + fulfillmentGroupId + }); + + return { + cart, + clientMutationId + }; +} diff --git a/packages/api-plugin-fulfillment/src/resolvers/Mutation/updateFulfillmentType.js b/packages/api-plugin-fulfillment/src/resolvers/Mutation/updateFulfillmentType.js new file mode 100644 index 00000000000..a19ead70dd8 --- /dev/null +++ b/packages/api-plugin-fulfillment/src/resolvers/Mutation/updateFulfillmentType.js @@ -0,0 +1,34 @@ +import { decodeShopOpaqueId, decodeFulfillmentGroupOpaqueId } from "../../xforms/id.js"; +import updateFulfillmentTypeMutation from "../../mutations/updateFulfillmentType.js"; +/** + * @name Mutation/updateFulfillmentType + * @method + * @memberof Cart/GraphQL + * @summary resolver for the updateFulfillmentType GraphQL mutation + * @param {Object} parentResult - unused + * @param {Object} args.input - an object of all mutation arguments that were sent by the client + * @param {String} args.input.fulfillmentGroupId - The fulfillment group to be updated + * @param {String} args.input.shopId - The ShopId to which the fulfillment group belongs + * @param {String} args.input.name - The fulfillment group name to be updated + * @param {String} args.input.enabled - Flag to enable/disable group + * @param {String} [args.input.clientMutationId] - An optional string identifying the mutation call + * @param {Object} context - an object containing the per-request state + * @returns {Promise} updateFulfillmentTypePayload + */ +export default async function updateFulfillmentType(parentResult, { input }, context) { + const { groupInfo, clientMutationId = null } = input; + const { shopId: opaqueShopId, fulfillmentGroupId: opaqueFulfillmentGroupId } = groupInfo; + + const fulfillmentGroupId = decodeFulfillmentGroupOpaqueId(opaqueFulfillmentGroupId); + const shopId = decodeShopOpaqueId(opaqueShopId); + const { group } = await updateFulfillmentTypeMutation(context, { + ...groupInfo, + shopId, + fulfillmentGroupId + }); + + return { + group, + clientMutationId + }; +} diff --git a/packages/api-plugin-fulfillment/src/resolvers/Query/getFulfillmentMethods.js b/packages/api-plugin-fulfillment/src/resolvers/Query/getFulfillmentMethods.js new file mode 100644 index 00000000000..6ca46d0a053 --- /dev/null +++ b/packages/api-plugin-fulfillment/src/resolvers/Query/getFulfillmentMethods.js @@ -0,0 +1,32 @@ +import getPaginatedResponseFromAggregate from "@reactioncommerce/api-utils/graphql/getPaginatedResponseFromAggregate.js"; +import wasFieldRequested from "@reactioncommerce/api-utils/graphql/wasFieldRequested.js"; +import { decodeShopOpaqueId, decodeFulfillmentGroupOpaqueId } from "../../xforms/id.js"; + +/** + * @name Query/getFulfillmentMethods + * @method + * @memberof Fulfillment/Query + * @summary Query for a list of fulfillment methods + * @param {Object} _ - unused + * @param {Object} args - an object of all arguments that were sent by the client + * @param {String} args.shopId - Shop ID to get records for + * @param {Object} context - an object containing the per-request state + * @param {Object} info Info about the GraphQL request + * @returns {Promise} Fulfillment methods + */ +export default async function getFulfillmentMethods(_, args, context, info) { + const { shopId: opaqueShopId, fulfillmentTypeId: opaqueFulfillmentId, ...connectionArgs } = args; + + const shopId = decodeShopOpaqueId(opaqueShopId); + const fulfillmentTypeId = decodeFulfillmentGroupOpaqueId(opaqueFulfillmentId); + + const { collection, pipeline } = await context.queries.getFulfillmentMethods(context, { + shopId, fulfillmentTypeId + }); + + return getPaginatedResponseFromAggregate(collection, pipeline, connectionArgs, { + includeHasNextPage: wasFieldRequested("pageInfo.hasNextPage", info), + includeHasPreviousPage: wasFieldRequested("pageInfo.hasPreviousPage", info), + includeTotalCount: wasFieldRequested("totalCount", info) + }); +} diff --git a/packages/api-plugin-fulfillment/src/resolvers/Query/getFulfillmentType.js b/packages/api-plugin-fulfillment/src/resolvers/Query/getFulfillmentType.js new file mode 100644 index 00000000000..e1c9bd0f022 --- /dev/null +++ b/packages/api-plugin-fulfillment/src/resolvers/Query/getFulfillmentType.js @@ -0,0 +1,28 @@ +import { decodeFulfillmentGroupOpaqueId, decodeShopOpaqueId } from "../../xforms/id.js"; + +/** + * @name Query/getFulfillmentType + * @method + * @memberof Fulfillment/Query + * @summary Query for a single fulfillment type + * @param {Object} _ - unused + * @param {Object} args - an object of all arguments that were sent by the client + * @param {String} args.fulfillmentTypeId - Fulfillment type ID to get the record of + * @param {String} args.shopId - Shop ID to get record for + * @param {Object} context - an object containing the per-request state + * @returns {Promise} Fulfillment type + */ +export default async function getFulfillmentType(_, args, context) { + const { + fulfillmentTypeId: opaqueTypeId, + shopId: opaqueShopId + } = args; + + const fulfillmentTypeId = decodeFulfillmentGroupOpaqueId(opaqueTypeId); + const shopId = decodeShopOpaqueId(opaqueShopId); + + return context.queries.getFulfillmentType(context, { + fulfillmentTypeId, + shopId + }); +} diff --git a/packages/api-plugin-fulfillment/src/resolvers/Query/getFulfillmentTypes.js b/packages/api-plugin-fulfillment/src/resolvers/Query/getFulfillmentTypes.js new file mode 100644 index 00000000000..f37eb83c2db --- /dev/null +++ b/packages/api-plugin-fulfillment/src/resolvers/Query/getFulfillmentTypes.js @@ -0,0 +1,31 @@ +import getPaginatedResponse from "@reactioncommerce/api-utils/graphql/getPaginatedResponse.js"; +import wasFieldRequested from "@reactioncommerce/api-utils/graphql/wasFieldRequested.js"; +import { decodeShopOpaqueId } from "../../xforms/id.js"; + +/** + * @name Query/FulfillmenTypes + * @method + * @memberof Fulfillment/Query + * @summary Query for a list of fulfillment types + * @param {Object} _ - unused + * @param {Object} args - an object of all arguments that were sent by the client + * @param {String} args.shopId - Shop ID to get records for + * @param {Object} context - an object containing the per-request state + * @param {Object} info Info about the GraphQL request + * @returns {Promise} Fulfillment methods + */ +export default async function getFulfillmentTypes(_, args, context, info) { + const { shopId: opaqueShopId, ...connectionArgs } = args; + + const shopId = decodeShopOpaqueId(opaqueShopId); + + const cursor = await context.queries.getFulfillmentTypes(context, { + shopId + }); + + return getPaginatedResponse(cursor, connectionArgs, { + includeHasNextPage: wasFieldRequested("pageInfo.hasNextPage", info), + includeHasPreviousPage: wasFieldRequested("pageInfo.hasPreviousPage", info), + includeTotalCount: wasFieldRequested("totalCount", info) + }); +} diff --git a/packages/api-plugin-fulfillment/src/resolvers/Query/index.js b/packages/api-plugin-fulfillment/src/resolvers/Query/index.js new file mode 100644 index 00000000000..fa4e54c3eda --- /dev/null +++ b/packages/api-plugin-fulfillment/src/resolvers/Query/index.js @@ -0,0 +1,9 @@ +import getFulfillmentType from "./getFulfillmentType.js"; +import getFulfillmentTypes from "./getFulfillmentTypes.js"; +import getFulfillmentMethods from "./getFulfillmentMethods.js"; + +export default { + getFulfillmentType, + getFulfillmentTypes, + getFulfillmentMethods +}; diff --git a/packages/api-plugin-fulfillment/src/resolvers/index.js b/packages/api-plugin-fulfillment/src/resolvers/index.js new file mode 100644 index 00000000000..63b65a3443d --- /dev/null +++ b/packages/api-plugin-fulfillment/src/resolvers/index.js @@ -0,0 +1,19 @@ +import FulfillmentMethod from "./FulfillmentMethod/index.js"; +import Mutation from "./Mutation/index.js"; +import Query from "./Query/index.js"; + +/** + * Fulfillment related GraphQL resolvers + * @namespace Fulfillment/GraphQL + */ + +export default { + FulfillmentMethod, + Mutation, + Query, + AdditionalData: { + __resolveType(obj) { + return obj.gqlType; + } + } +}; diff --git a/packages/api-plugin-fulfillment/src/schemas/index.js b/packages/api-plugin-fulfillment/src/schemas/index.js new file mode 100644 index 00000000000..30096f92e54 --- /dev/null +++ b/packages/api-plugin-fulfillment/src/schemas/index.js @@ -0,0 +1,5 @@ +import importAsString from "@reactioncommerce/api-utils/importAsString.js"; + +const schema = importAsString("./schema.graphql"); + +export default [schema]; diff --git a/packages/api-plugin-fulfillment/src/schemas/schema.graphql b/packages/api-plugin-fulfillment/src/schemas/schema.graphql new file mode 100644 index 00000000000..5a7369daa09 --- /dev/null +++ b/packages/api-plugin-fulfillment/src/schemas/schema.graphql @@ -0,0 +1,414 @@ +"Allowed fulfillment types, extended by fulfillment-type plugins" +enum FulfillmentType { + "Default fulfillment type when none is decided by user" + undecided +} + +"Default empty object for additionalData" +type emptyData { + gqlType: String + emptyData: Boolean +} + +"Union of Additional Data fields" +union AdditionalData = emptyData + +""" +A single fulfillment method. Fulfillment methods are shown to shoppers along with a quote for them, +and the shopper chooses one method per fulfillment group per cart during checkout. +""" +type FulfillmentMethod implements Node { + "The fulfillment method ID" + _id: ID! + + "A carrier name" + carrier: String + + "The name of this method, for display in the user interface" + displayName: String! + + """ + The fulfillment types for which this method may be used. For example, `shipping` or `digital`. + """ + fulfillmentTypes: [FulfillmentType]! + + "The group to which this method belongs" + group: String + + "The name of this method, a unique identifier" + name: String! + + "Additional data provided by this method" + methodAdditionalData: AdditionalData +} + + +"Defines a fulfillment method in general." +type FulfillmentMethodObj { + "The fulfillment method ID" + _id: ID! + + "The cost of this fulfillment method to the shop, if you track this" + cost: Float + + """ + The fulfillment types for which this method may be used. For example, `shipping` or `digital`. + """ + fulfillmentTypes: [FulfillmentType] + + "The group to which this method belongs" + group: String! + + "A fixed price to charge for handling costs when this fulfillment method is selected for an order" + handling: Float! + + "Include this as a fulfillment option shown to shoppers during checkout?" + enabled: Boolean! + + "The name of this method, for display in the user interface" + label: String! + + "The name of this method, a unique identifier" + name: String! + + "The common-name for this method, to group all variants of the same method" + fulfillmentMethod: String + + "A fixed price to charge for fulfillment costs when this fulfillment method is selected for an order" + rate: Float! +} + +"Provider info" +type ProviderInfo{ + "Name of the provider" + name: String! + + "Label of the provider" + label: String! + + "Flag defining enabled/disabled status" + enabled: Boolean +} +"Fulfillment type root object" +type FulfillmentTypeObj implements Node { + "The Fulfillment Type ID" + _id: ID! + + "The user provided name of the fulfillment type" + name: String! + + "Shop ID" + shopId: ID! + + "ProviderInfo" + provider: ProviderInfo + + "Fulfillment type" + fulfillmentType: String + + "Fulfillment methods" + methods: [FulfillmentMethodObj] +} + +"Custom Fulfillment method object " +type CustomFulfillmentMethodObj implements Node { + "The Fulfillment Type ID" + fulfillmentTypeId: ID! + + "Shop ID" + shopId: ID! + + "The fulfillment method ID" + _id: ID! + + "The cost of this fulfillment method to the shop, if you track this" + cost: Float + + """ + The fulfillment types for which this method may be used. For example, `shipping` or `digital`. + """ + fulfillmentTypes: [FulfillmentType] + + "The group to which this method belongs" + group: String! + + "A fixed price to charge for handling costs when this fulfillment method is selected for an order" + handling: Float! + + "Include this as a fulfillment option shown to shoppers during checkout?" + enabled: Boolean! + + "The name of this method, for display in the user interface" + label: String! + + "The name of this method, a unique identifier" + name: String! + + "The common-name for this method, to group all variants of the same method" + fulfillmentMethod: String + + "A fixed price to charge for fulfillment costs when this fulfillment method is selected for an order" + rate: Float! +} + +"Input needed to select a fulfillment option for a single fulfillment group on a cart" +input updateFulfillmentTypeInputInfo { + "The shop to which this group belongs" + shopId: ID! + + "The group to update" + fulfillmentGroupId: ID! + + "The type name to be updated" + name: String! + + "The label to be updated" + label: String! + + "The displayMessage to be updated" + displayMessageType: String + + "Flag to define if the group should be enabled/disabled" + enabled: Boolean +} +"Input needed to select a fulfillment option for a single fulfillment group on a cart" +input updateFulfillmentTypeInput { + "The shop to which this group belongs" + groupInfo: updateFulfillmentTypeInputInfo! + + "An optional string identifying the mutation call, which will be returned in the response payload" + clientMutationId: String +} +"The updated group info from updateFulfillmentType" +type updateFulfillmentTypeGroup { + "The shop to which this group belongs" + shopId: ID! + + "The group which was updated" + fulfillmentGroupId: ID! + + "The updated group name" + name: String! + + "The updated group label" + label: String! + + "Flag defining enabled/disabled status" + enabled: Boolean +} +"The response from the `updateFulfillmentType` mutation" +type updateFulfillmentTypePayload { + "The updated Group" + group: updateFulfillmentTypeGroup! + + "The same string you sent with the mutation params, for matching mutation calls with their responses" + clientMutationId: String +} + + + +"Method info input" +input MethodInfoInput{ + "Name of the provider" + name: String! + + "Label of the provider" + label: String! + + "Fulfillmenttypes" + fulfillmentTypes: [String]! + + "Group" + group: String! + + "Display message Method" + displayMessageMethod: String + + "Cost" + cost: Int + + "Handling" + handling: Int! + + "Rate" + rate: Int! + + "Flag defining enabled/disabled status" + enabled: Boolean! + + "FulfillmentMethod" + fulfillmentMethod: String +} + + +"The updated group infofrom updateFulfillmentMethod" +input updateFulfillmentMethodInfo { + "Shop Id" + shopId: String! + + "The group which was updated" + fulfillmentTypeId: String! + + "The method which has to be updated" + methodId: String! + + "Method info" + method: MethodInfoInput +} +input updateFulfillmentMethodInput { + "Group Info Fulfillment Method" + groupInfo: updateFulfillmentMethodInfo! + + "An optional string identifying the mutation call, which will be returned in the response payload" + clientMutationId: String +} +type updateFulfillmentMethodPayload { + "The inserted Group" + group: FulfillmentMethodObj! + + "The same string you sent with the mutation params, for matching mutation calls with their responses" + clientMutationId: String +} + +extend type Mutation { + "Updates the Name and Enabled fields for the provided Fulfillment Type" + updateFulfillmentType( + "Mutation input" + input: updateFulfillmentTypeInput! + ): updateFulfillmentTypePayload! + + "Updates the provided Fulfillment Method" + updateFulfillmentMethod( + "Mutation input" + input: updateFulfillmentMethodInput! + ): updateFulfillmentMethodPayload! +} + +extend type Query { + "Get a fulfillment type" + getFulfillmentType( + "Fulfillment type id" + fulfillmentTypeId: ID! + + "Shop ID" + shopId: ID! + ): FulfillmentTypeObj! + + "Get all fulfillment types" + getFulfillmentTypes( + "Shop ID" + shopId: ID! + + "Return only results that come after this cursor. Use this with `first` to specify the number of results to return." + after: ConnectionCursor, + + "Return only results that come before this cursor. Use this with `last` to specify the number of results to return." + before: ConnectionCursor, + + "Return at most this many results. This parameter may be used with either `after` or `offset` parameters." + first: ConnectionLimitInt, + + "Return at most this many results. This parameter may be used with the `before` parameter." + last: ConnectionLimitInt, + + "Return only results that come after the Nth result. This parameter may be used with the `first` parameter." + offset: Int, + ): FulfillmentTypeObjConnection! + + "Get all fulfillment methods for the given type" + getFulfillmentMethods( + "Shop ID" + shopId: ID! + + "Fulfillment Type ID" + fulfillmentTypeId: ID! + + "Return only results that come after this cursor. Use this with `first` to specify the number of results to return." + after: ConnectionCursor, + + "Return only results that come before this cursor. Use this with `last` to specify the number of results to return." + before: ConnectionCursor, + + "Return at most this many results. This parameter may be used with either `after` or `offset` parameters." + first: ConnectionLimitInt, + + "Return at most this many results. This parameter may be used with the `before` parameter." + last: ConnectionLimitInt, + + "Return only results that come after the Nth result. This parameter may be used with the `first` parameter." + offset: Int, + ): FulfillmentMethodObjConnection! +} + + +#### +# Connections +#### + +"A connection edge in which each node is a `FulfillmentTypeObj` object" +type FulfillmentTypeObjEdge { + "The cursor that represents this node in the paginated results" + cursor: ConnectionCursor! + + "The fulfillment method" + node: FulfillmentTypeObj +} + +"A connection edge in which each node is a `CustomFulfillmentMethodObj` object" +type CustomFulfillmentMethodObjEdge { + "The cursor that represents this node in the paginated results" + cursor: ConnectionCursor! + + "The fulfillment method" + node: CustomFulfillmentMethodObj +} + +""" +Wraps a list of FulfillmentTypes, providing pagination cursors and information. + +For information about what Relay-compatible connections are and how to use them, see the following articles: +- [Relay Connection Documentation](https://facebook.github.io/relay/docs/en/graphql-server-specification.html#connections) +- [Relay Connection Specification](https://facebook.github.io/relay/graphql/connections.htm) +- [Using Relay-style Connections With Apollo Client](https://www.apollographql.com/docs/react/recipes/pagination.html) +""" +type FulfillmentTypeObjConnection { + "The list of nodes that match the query, wrapped in an edge to provide a cursor string for each" + edges: [FulfillmentTypeObjEdge] + + """ + You can request the `nodes` directly to avoid the extra wrapping that `NodeEdge` has, + if you know you will not need to paginate the results. + """ + nodes: [FulfillmentTypeObj] + + "Information to help a client request the next or previous page" + pageInfo: PageInfo! + + "The total number of nodes that match your query" + totalCount: Int! +} + +""" +Wraps a list of Cusom FulfillmentMethods, providing pagination cursors and information. + +For information about what Relay-compatible connections are and how to use them, see the following articles: +- [Relay Connection Documentation](https://facebook.github.io/relay/docs/en/graphql-server-specification.html#connections) +- [Relay Connection Specification](https://facebook.github.io/relay/graphql/connections.htm) +- [Using Relay-style Connections With Apollo Client](https://www.apollographql.com/docs/react/recipes/pagination.html) +""" +type FulfillmentMethodObjConnection { + "The list of nodes that match the query, wrapped in an edge to provide a cursor string for each" + edges: [CustomFulfillmentMethodObjEdge] + + """ + You can request the `nodes` directly to avoid the extra wrapping that `NodeEdge` has, + if you know you will not need to paginate the results. + """ + nodes: [CustomFulfillmentMethodObj] + + "Information to help a client request the next or previous page" + pageInfo: PageInfo! + + "The total number of nodes that match your query" + totalCount: Int! +} diff --git a/packages/api-plugin-fulfillment/src/simpleSchemas.js b/packages/api-plugin-fulfillment/src/simpleSchemas.js new file mode 100644 index 00000000000..8ecd68298cd --- /dev/null +++ b/packages/api-plugin-fulfillment/src/simpleSchemas.js @@ -0,0 +1,133 @@ +import SimpleSchema from "simpl-schema"; + +/** + * @name MethodEmptyData + * @memberof Schemas + * @type {SimpleSchema} + * @summary Defines Empty placeholder + * @property {String} gqlType Defines the method type + * @property {Boolean} emptyData Empty Data fields + */ +export const MethodEmptyData = new SimpleSchema({ + gqlType: String, + emptyData: { + type: Boolean, + optional: true + } +}); + + +export const FulfillmentMethodSchema = new SimpleSchema({ + "cost": { + type: Number, + optional: true + }, + "fulfillmentTypes": { + type: Array, + minCount: 1 + }, + "fulfillmentTypes.$": String, + "group": String, + "handling": Number, + "enabled": Boolean, + "label": String, + "name": String, + "fulfillmentMethod": { + type: String, + optional: true + }, + "displayMessageMethod": { + type: String, + optional: true + }, + "rate": Number +}); + +const ProviderSchema = new SimpleSchema({ + enabled: Boolean, + label: String, + name: String +}); + +export const fulfillmentTypeSchema = new SimpleSchema({ + "name": String, + "shopId": String, + "provider": { + type: ProviderSchema + }, + "fulfillmentType": String, + "displayMessageType": { + type: String, + optional: true + }, + "methods": { + type: Array, + optional: true + }, + "methods.$": { + type: FulfillmentMethodSchema + } +}); + +/** + * @summary Extend the schema with updated allowedValues + * @param {Object} schemas Schemas from context passed in + * @param {String[]} allFulfillmentTypesArray Array of all fulfillment types + * @returns {undefined} + */ +export function extendFulfillmentSchemas(schemas, allFulfillmentTypesArray) { + const schemaProductExtension = { + "supportedFulfillmentTypes.$": { + allowedValues: allFulfillmentTypesArray + } + }; + schemas.Product.extend(schemaProductExtension); + + const schemaCatalogProductExtension = { + "supportedFulfillmentTypes.$": { + allowedValues: allFulfillmentTypesArray + } + }; + schemas.CatalogProduct.extend(schemaCatalogProductExtension); + + const schemaShipmentExtension = { + type: { + allowedValues: allFulfillmentTypesArray + } + }; + schemas.Shipment.extend(schemaShipmentExtension); + + const schemaCartItemExtension = { + "selectedFulfillmentType": { + allowedValues: allFulfillmentTypesArray + }, + "supportedFulfillmentTypes.$": { + allowedValues: allFulfillmentTypesArray + } + }; + schemas.CartItem.extend(schemaCartItemExtension); + + const schemaExtension = { + type: { + allowedValues: allFulfillmentTypesArray + } + }; + const schemaExtensionCommonOrder = { + fulfillmentType: { + allowedValues: allFulfillmentTypesArray + } + }; + schemas.CommonOrder.extend(schemaExtensionCommonOrder); + schemas.orderFulfillmentGroupInputSchema.extend(schemaExtension); + schemas.OrderFulfillmentGroup.extend(schemaExtension); + + const schemaExtensionMethodAdditionalData = { + methodAdditionalData: { + type: SimpleSchema.oneOf(MethodEmptyData), + optional: true, + label: "Method specific additional data" + } + }; + schemas.SelectedFulfillmentOption.extend(schemaExtensionMethodAdditionalData); + schemas.ShippingMethod.extend(schemaExtensionMethodAdditionalData); +} diff --git a/packages/api-plugin-fulfillment/src/startup.js b/packages/api-plugin-fulfillment/src/startup.js new file mode 100644 index 00000000000..509d20ea20d --- /dev/null +++ b/packages/api-plugin-fulfillment/src/startup.js @@ -0,0 +1,30 @@ +/** + * @summary Called on startup to creates root entry for undecided fulfillment type + * @param {Object} context Startup context + * @param {Object} context.collections Map of MongoDB collections + * @returns {undefined} + */ +export default async function fulfillmentTypeUndecidedStartup(context) { + const { collections } = context; + const { Fulfillment } = collections; + + context.appEvents.on("afterShopCreate", async (payload) => { + const { shop } = payload; + const shopId = shop._id; + + const undecidedRecord = await Fulfillment.findOne({ fulfillmentType: "undecided", shopId }); + if (!undecidedRecord) { + const groupInfo = { + name: "Undecided Group", + shopId, + provider: { + enabled: true, + label: "Undecided", + name: "undecided" + }, + fulfillmentType: "undecided" + }; + await context.mutations.createFulfillmentType(context.getInternalContext(), groupInfo); + } + }); +} diff --git a/packages/api-plugin-fulfillment/src/tests/factory.js b/packages/api-plugin-fulfillment/src/tests/factory.js new file mode 100644 index 00000000000..427e1346b63 --- /dev/null +++ b/packages/api-plugin-fulfillment/src/tests/factory.js @@ -0,0 +1,20 @@ +import { createFactoryForSchema, Factory } from "@reactioncommerce/data-factory"; + +import { + Cart, + ShipmentQuote +} from "@reactioncommerce/api-plugin-carts/src/simpleSchemas.js"; + +const schemasToAddToFactory = { + Cart, + ShipmentQuote +}; + +// Adds each to `Factory` object. For example, `Factory.Cart` +// will be the factory that builds an object that matches the +// `Cart` schema. +Object.keys(schemasToAddToFactory).forEach((key) => { + createFactoryForSchema(key, schemasToAddToFactory[key]); +}); + +export default Factory; diff --git a/packages/api-plugin-fulfillment/src/util/extendCommonOrder.js b/packages/api-plugin-fulfillment/src/util/extendCommonOrder.js new file mode 100644 index 00000000000..b9a019d408a --- /dev/null +++ b/packages/api-plugin-fulfillment/src/util/extendCommonOrder.js @@ -0,0 +1,93 @@ +import _ from "lodash"; +import ReactionError from "@reactioncommerce/reaction-error"; +import tagsForCatalogProducts from "@reactioncommerce/api-utils/tagsForCatalogProducts.js"; + +/** + * @name mergeProductAndVariants + * @summary Merges a product and its variants + * @param {Object} productAndVariants - The product and its variants + * @returns {Object} - The merged product and variants + */ +function mergeProductAndVariants(productAndVariants) { + const { product, parentVariant, variant } = productAndVariants; + + // Filter out unnecessary product props + const productProps = _.omit(product, [ + "variants", "media", "metafields", "parcel", " primaryImage", "socialMetadata", "customAttributes" + ]); + + // Filter out unnecessary variant props + const variantExcludeProps = ["media", "parcel", "primaryImage", "customAttributes"]; + const variantProps = _.omit(variant, variantExcludeProps); + + // If an option has been added to the cart + if (parentVariant) { + // Filter out unnecessary parent variant props + const parentVariantProps = _.omit(parentVariant, variantExcludeProps); + + return { + ...productProps, + ...parentVariantProps, + ...variantProps + }; + } + + return { + ...productProps, + ...variantProps + }; +} + +/** + * @name extendCommonOrder + * @summary Add extra properties to all CommonOrder items, which will be used to + * determine any applicable shipping restrictions. + * @param {Object} context - an object containing the per-request state + * @param {Object} commonOrder - details about the purchase a user wants to make. + * @returns {Object|null} The CommonOrder, with each item in `.items` extended. + */ +export default async function extendCommonOrder(context, commonOrder) { + const { collections: { Tags }, getFunctionsOfType, queries } = context; + const { items: orderItems } = commonOrder; + const extendedOrderItems = []; + + // Products in the Catalog collection are the source of truth, therefore use them + // as the source of data instead of what is coming from the client. + const catalogProductsAndVariants = await queries.findCatalogProductsAndVariants(context, orderItems); + const allProductsTags = await tagsForCatalogProducts(Tags, catalogProductsAndVariants); + + for (const orderLineItem of orderItems) { + const productAndVariants = catalogProductsAndVariants.find((catProduct) => catProduct.product.productId === orderLineItem.productId); + + if (!productAndVariants) { + throw new ReactionError("not-found", "Catalog product not found"); + } + + const extendedOrderItem = { + ...orderLineItem, + ...mergeProductAndVariants(productAndVariants) + }; + + // Fetch product tags + const tagInfo = allProductsTags.find((info) => info.productId === extendedOrderItem.productId); + if (tagInfo) { + extendedOrderItem.tags = tagInfo.tags; + } + + // Fetch custom attributes + // We need to run each of these functions in a series, rather than in parallel, because + // we are mutating the same object on each pass. It is recommended to disable `no-await-in-loop` + // eslint rules when the output of one iteration might be used as input in another iteration, such as this case here. + // See https://eslint.org/docs/rules/no-await-in-loop#when-not-to-use-it + for (const customAttributesFunc of getFunctionsOfType("addShippingRestrictionCustomAttributes")) { + await customAttributesFunc(extendedOrderItem, productAndVariants); // eslint-disable-line + } + + extendedOrderItems.push(extendedOrderItem); + } + + return { + ...commonOrder, + items: extendedOrderItems + }; +} diff --git a/packages/api-plugin-fulfillment/src/util/getCartById.js b/packages/api-plugin-fulfillment/src/util/getCartById.js new file mode 100644 index 00000000000..3142f9d4f69 --- /dev/null +++ b/packages/api-plugin-fulfillment/src/util/getCartById.js @@ -0,0 +1,34 @@ +import hashToken from "@reactioncommerce/api-utils/hashToken.js"; +import ReactionError from "@reactioncommerce/reaction-error"; + +/** + * @summary Gets a cart from the db by ID. If there is an account for the request, verifies that the + * account has permission to access the cart. Optionally throws an error if not found. + * @param {Object} context Object defining the request state + * @param {String} cartId The cart ID + * @param {Object} [options] Options + * @param {String} [options.cartToken] Cart token, required if it's an anonymous cart + * @param {Boolean} [options.throwIfNotFound] Default false. Throw a not-found error rather than return null `cart` + * @returns {Object|null} The cart document, or null if not found and `throwIfNotFound` was false + */ +export default async function getCartById(context, cartId, { cartToken, throwIfNotFound = false } = {}) { + const { accountId, collections } = context; + const { Cart } = collections; + + const selector = { _id: cartId }; + + if (cartToken) { + selector.anonymousAccessToken = hashToken(cartToken); + } + + const cart = await Cart.findOne(selector); + if (!cart && throwIfNotFound) { + throw new ReactionError("not-found", "Cart not found"); + } + + if (cart && cart.accountId && cart.accountId !== accountId) { + throw new ReactionError("access-denied", "Access Denied"); + } + + return cart || null; +} diff --git a/packages/api-plugin-fulfillment/src/xforms/id.js b/packages/api-plugin-fulfillment/src/xforms/id.js new file mode 100644 index 00000000000..5697bf0747d --- /dev/null +++ b/packages/api-plugin-fulfillment/src/xforms/id.js @@ -0,0 +1,18 @@ +import decodeOpaqueIdForNamespace from "@reactioncommerce/api-utils/decodeOpaqueIdForNamespace.js"; +import encodeOpaqueId from "@reactioncommerce/api-utils/encodeOpaqueId.js"; + +const namespaces = { + Shop: "reaction/shop", + Cart: "reaction/cart", + FulfillmentGroup: "reaction/fulfillmentGroup", + FulfillmentMethod: "reaction/fulfillmentMethod" +}; + +export const encodeShopOpaqueId = encodeOpaqueId(namespaces.Shop); +export const encodeFulfillmentMethodOpaqueId = encodeOpaqueId(namespaces.FulfillmentMethod); +export const encodeFulfillmentGroupOpaqueId = encodeOpaqueId(namespaces.FulfillmentGroup); + +export const decodeShopOpaqueId = decodeOpaqueIdForNamespace(namespaces.Shop); +export const decodeCartOpaqueId = decodeOpaqueIdForNamespace(namespaces.Cart); +export const decodeFulfillmentGroupOpaqueId = decodeOpaqueIdForNamespace(namespaces.FulfillmentGroup); +export const decodeFulfillmentMethodOpaqueId = decodeOpaqueIdForNamespace(namespaces.FulfillmentMethod); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 3134e0f4535..52fbd4cbee6 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -225,16 +225,16 @@ importers: '@reactioncommerce/api-plugin-payments-example': link:../../packages/api-plugin-payments-example '@reactioncommerce/api-plugin-payments-stripe-sca': link:../../packages/api-plugin-payments-stripe-sca '@reactioncommerce/api-plugin-pricing-simple': link:../../packages/api-plugin-pricing-simple - '@reactioncommerce/api-plugin-products': link:../../packages/api-plugin-products + '@reactioncommerce/api-plugin-products': 1.3.0_graphql@14.7.0 '@reactioncommerce/api-plugin-settings': link:../../packages/api-plugin-settings '@reactioncommerce/api-plugin-shipments': link:../../packages/api-plugin-shipments '@reactioncommerce/api-plugin-shipments-flat-rate': link:../../packages/api-plugin-shipments-flat-rate '@reactioncommerce/api-plugin-shops': link:../../packages/api-plugin-shops '@reactioncommerce/api-plugin-simple-schema': link:../../packages/api-plugin-simple-schema '@reactioncommerce/api-plugin-sitemap-generator': link:../../packages/api-plugin-sitemap-generator - '@reactioncommerce/api-plugin-surcharges': link:../../packages/api-plugin-surcharges + '@reactioncommerce/api-plugin-surcharges': 1.1.7_graphql@14.7.0 '@reactioncommerce/api-plugin-system-information': link:../../packages/api-plugin-system-information - '@reactioncommerce/api-plugin-tags': link:../../packages/api-plugin-tags + '@reactioncommerce/api-plugin-tags': 1.2.0_graphql@14.7.0 '@reactioncommerce/api-plugin-taxes': link:../../packages/api-plugin-taxes '@reactioncommerce/api-plugin-taxes-flat-rate': link:../../packages/api-plugin-taxes-flat-rate '@reactioncommerce/api-plugin-translations': link:../../packages/api-plugin-translations @@ -485,7 +485,7 @@ importers: simpl-schema: ^1.12.0 dependencies: '@reactioncommerce/api-plugin-address-validation': link:../api-plugin-address-validation - '@reactioncommerce/api-plugin-tags': link:../api-plugin-tags + '@reactioncommerce/api-plugin-tags': 1.2.0_graphql@15.8.0 '@reactioncommerce/api-utils': link:../api-utils '@reactioncommerce/logger': link:../logger '@reactioncommerce/random': link:../random @@ -673,6 +673,27 @@ importers: '@reactioncommerce/babel-remove-es-create-require': 1.0.0_@babel+core@7.19.0 '@reactioncommerce/data-factory': 1.0.1 + packages/api-plugin-fulfillment: + specifiers: + '@reactioncommerce/api-plugin-carts': ^1.0.0 + '@reactioncommerce/api-utils': ^1.16.9 + '@reactioncommerce/data-factory': ~1.0.1 + '@reactioncommerce/logger': ^1.1.3 + '@reactioncommerce/random': ^1.0.2 + '@reactioncommerce/reaction-error': ^1.0.1 + lodash: ^4.17.21 + simpl-schema: ^1.12.2 + dependencies: + '@reactioncommerce/api-utils': link:../api-utils + '@reactioncommerce/logger': link:../logger + '@reactioncommerce/random': link:../random + '@reactioncommerce/reaction-error': link:../reaction-error + lodash: 4.17.21 + simpl-schema: 1.12.3 + devDependencies: + '@reactioncommerce/api-plugin-carts': link:../api-plugin-carts + '@reactioncommerce/data-factory': 1.0.1 + packages/api-plugin-i18n: specifiers: '@reactioncommerce/api-utils': ^1.16.5 @@ -4591,6 +4612,108 @@ packages: /@protobufjs/utf8/1.1.0: resolution: {integrity: sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw==} + /@reactioncommerce/api-plugin-products/1.3.0_graphql@14.7.0: + resolution: {integrity: sha512-utK/z9MXJ7qcASwvUlFuej5O5fIDUBi5kWg90am91H5p0wa8NKLXNsVzV/MC8oQDSq+/ljhtAhZQsPOBajxr1g==} + engines: {node: '>=14.18.1'} + dependencies: + '@reactioncommerce/api-utils': 1.16.9_graphql@14.7.0 + '@reactioncommerce/logger': 1.1.5 + '@reactioncommerce/random': 1.0.2 + '@reactioncommerce/reaction-error': 1.0.1 + lodash: 4.17.21 + simpl-schema: 1.12.3 + transitivePeerDependencies: + - graphql + dev: false + + /@reactioncommerce/api-plugin-surcharges/1.1.7_graphql@14.7.0: + resolution: {integrity: sha512-UdaWUmFEUHfCaOAGHs31S3yZGufmD7zO+GpOTlC4U310oQjr+CNQ0b2H8QxmSoUKnjgQfO9SN9bjiF5qUcOU6g==} + engines: {node: '>=14.18.1'} + dependencies: + '@reactioncommerce/api-utils': 1.16.9_graphql@14.7.0 + '@reactioncommerce/random': 1.0.2 + '@reactioncommerce/reaction-error': 1.0.1 + simpl-schema: 1.12.3 + transitivePeerDependencies: + - graphql + dev: false + + /@reactioncommerce/api-plugin-tags/1.2.0_graphql@14.7.0: + resolution: {integrity: sha512-6SgTt+AvGJs/ThRq8tMEiEF0HvWShJhzRNjInM+CJ/mz/1U9fDVRxhecwWI7iYG9gqs30XNFRhjgNdJxJVO0SA==} + engines: {node: '>=14.18.1'} + dependencies: + '@reactioncommerce/api-utils': 1.16.9_graphql@14.7.0 + '@reactioncommerce/data-factory': 1.0.1 + '@reactioncommerce/db-version-check': 1.0.0 + '@reactioncommerce/file-collections': 0.9.3 + '@reactioncommerce/random': 1.0.2 + '@reactioncommerce/reaction-error': 1.0.1 + lodash: 4.17.21 + simpl-schema: 1.10.2 + transitivePeerDependencies: + - encoding + - graphql + - supports-color + dev: false + + /@reactioncommerce/api-plugin-tags/1.2.0_graphql@15.8.0: + resolution: {integrity: sha512-6SgTt+AvGJs/ThRq8tMEiEF0HvWShJhzRNjInM+CJ/mz/1U9fDVRxhecwWI7iYG9gqs30XNFRhjgNdJxJVO0SA==} + engines: {node: '>=14.18.1'} + dependencies: + '@reactioncommerce/api-utils': 1.16.9_graphql@15.8.0 + '@reactioncommerce/data-factory': 1.0.1 + '@reactioncommerce/db-version-check': 1.0.0 + '@reactioncommerce/file-collections': 0.9.3 + '@reactioncommerce/random': 1.0.2 + '@reactioncommerce/reaction-error': 1.0.1 + lodash: 4.17.21 + simpl-schema: 1.10.2 + transitivePeerDependencies: + - encoding + - graphql + - supports-color + dev: false + + /@reactioncommerce/api-utils/1.16.9_graphql@14.7.0: + resolution: {integrity: sha512-9jB684K3GISSkkTeiMIniYiFgAtHL1AosSD2GPVpGSpFIUVbEL93e8owEadSIugmJt8yOb3m1+YdThW+kvXlzQ==} + engines: {node: '>=12.14.1'} + dependencies: + '@jest/globals': 26.6.2 + '@reactioncommerce/logger': 1.1.5 + '@reactioncommerce/random': 1.0.2 + '@reactioncommerce/reaction-error': 1.0.1 + accounting-js: 1.1.1 + callsite: 1.0.0 + envalid: 6.0.2 + graphql-fields: 2.0.3 + graphql-relay: 0.9.0_graphql@14.7.0 + lodash: 4.17.21 + ramda: 0.27.2 + transliteration: 2.3.5 + transitivePeerDependencies: + - graphql + dev: false + + /@reactioncommerce/api-utils/1.16.9_graphql@15.8.0: + resolution: {integrity: sha512-9jB684K3GISSkkTeiMIniYiFgAtHL1AosSD2GPVpGSpFIUVbEL93e8owEadSIugmJt8yOb3m1+YdThW+kvXlzQ==} + engines: {node: '>=12.14.1'} + dependencies: + '@jest/globals': 26.6.2 + '@reactioncommerce/logger': 1.1.5 + '@reactioncommerce/random': 1.0.2 + '@reactioncommerce/reaction-error': 1.0.1 + accounting-js: 1.1.1 + callsite: 1.0.0 + envalid: 6.0.2 + graphql-fields: 2.0.3 + graphql-relay: 0.9.0_graphql@15.8.0 + lodash: 4.17.21 + ramda: 0.27.2 + transliteration: 2.3.5 + transitivePeerDependencies: + - graphql + dev: false + /@reactioncommerce/babel-remove-es-create-require/1.0.0_@babel+core@7.19.0: resolution: {integrity: sha512-yR1vMj76hK5D9/VcXjFpk2OMoYpQdbvEgGcJ79cEzTL/jkFwIs53Zl6PNBjcC608P8qHl5eypEYRwG7CCGLzwQ==} peerDependencies: @@ -4621,6 +4744,10 @@ packages: transitivePeerDependencies: - supports-color + /@reactioncommerce/db-version-check/1.0.0: + resolution: {integrity: sha512-68ypfiCvdsmMaLmzNpXjN52SmgyHHO2IjLOscBbqBlvq/rFCVLLxV5/WkiFnedmNesFoXCfwYhyGWyjwaqicuw==} + dev: false + /@reactioncommerce/eslint-config/2.2.2_ihki3xsivcrciip6mkoznh225e: resolution: {integrity: sha512-E4BlfLmQBr5Sjt3LP9tAbgCKXZ2rQ4/Lpvle6khHUcuh33NcuBEInN+uStY6koPHAvnKgwn6MZ0LTYzVnKwSVA==} engines: {node: '>=8.1.0'} @@ -4646,11 +4773,43 @@ packages: eslint-plugin-you-dont-need-lodash-underscore: 6.12.0 dev: true + /@reactioncommerce/file-collections/0.9.3: + resolution: {integrity: sha512-8HLEjvlhtgpOAQYQeHA6UGYJfbYoSuUtQAyo0E0DUUpgeViXiWgQ+T4DIK3NbUinlvA4Ny8FDtZCNEg5eHCfMw==} + dependencies: + '@babel/runtime-corejs2': 7.19.0 + content-disposition: 0.5.4 + debug: 4.3.4 + extend: 3.0.2 + path-parser: 6.1.0 + query-string: 6.14.1 + tus-js-client: 1.8.0 + tus-node-server: 0.3.2 + transitivePeerDependencies: + - encoding + - supports-color + dev: false + + /@reactioncommerce/logger/1.1.5: + resolution: {integrity: sha512-fG6wohv9NoX6ZjNzs0YcFRFxHyndO4VitMCrbz+8HrnM7LNJtQRzrUcR58RptagYixZ4GyiHY6WhmIfrMpNvLw==} + dependencies: + bunyan: 1.8.15 + bunyan-format: 0.2.1 + node-loggly-bulk: 2.2.5 + dev: false + /@reactioncommerce/nodemailer/5.0.5: resolution: {integrity: sha512-u4ontTETlROmLglkMDyouMXlX62NXOGfOUAd75Ilk3W4tcsRjRXX+g5C5B4mBCCcJB0wHn1yh/a4pOYkn81vUQ==} engines: {node: '>=4.0.0'} dev: false + /@reactioncommerce/random/1.0.2: + resolution: {integrity: sha512-02eARZh6FQ45tEXXZUwnz90XWQasLpnV140CcuOl/L3dhEL4Emu2oCZemVhKvNb2JMmm5vOG2Y0CUb12xAx73w==} + dev: false + + /@reactioncommerce/reaction-error/1.0.1: + resolution: {integrity: sha512-l1HyDTFw4m1j8EPWNA/bbF2GZZaLYQjwmgniSjjM1cgnhfjCqeLywi501oPkmH8E84Jh0EH+4ADhNCcZ4m4gug==} + dev: false + /@sinclair/typebox/0.24.41: resolution: {integrity: sha512-TJCgQurls4FipFvHeC+gfAzb+GGstL0TDwYJKQVtTeSvJIznWzP7g3bAd5gEBlr8+bIxqnWS9VGVWREDhmE8jA==} dev: true @@ -9013,6 +9172,15 @@ packages: graphql: 14.7.0 dev: false + /graphql-relay/0.9.0_graphql@15.8.0: + resolution: {integrity: sha512-yNJLCqcjz0XpzpmmckRJCSK8a2ZLwTurwrQ09UyGftONh52PbrGpK1UO4yspvj0c7pC+jkN4ZUqVXG3LRrWkXQ==} + engines: {node: ^12.20.0 || ^14.15.0 || >= 15.9.0} + peerDependencies: + graphql: ^15.5.3 + dependencies: + graphql: 15.8.0 + dev: false + /graphql-schema-linter/3.0.0_graphql@16.6.0: resolution: {integrity: sha512-nXsyFvcqrD9/QWgimvJjvRBsUXHw8O7XoPLhpnGZd9eX6r2ZXsOBCGhTzF9ZrFtWosCBEfBGU2xpFUPe0qCHVg==} hasBin: true @@ -9064,6 +9232,11 @@ packages: dependencies: iterall: 1.3.0 + /graphql/15.8.0: + resolution: {integrity: sha512-5gghUc24tP9HRznNpV2+FIoq3xKkj5dTQqf4v0CpdPbFVwFkWoxOM+o+2OC9ZSvjEMTjfmG9QT+gcvggTwW1zw==} + engines: {node: '>= 10.x'} + dev: false + /graphql/16.6.0: resolution: {integrity: sha512-KPIBPDlW7NxrbT/eh4qPXz5FiFdL5UbaA0XUNz2Rp3Z3hqBSkbj0GVjwFDztsWVauZUWsbKHgMg++sk8UX0bkw==} engines: {node: ^12.22.0 || ^14.16.0 || ^16.0.0 || >=17.0.0} @@ -12358,6 +12531,10 @@ packages: engines: {node: '>=8'} dev: false + /ramda/0.27.2: + resolution: {integrity: sha512-SbiLPU40JuJniHexQSAgad32hfwd+DRUdwF2PlVuI5RZD0/vahUco7R8vD86J/tcEKKF9vZrUVwgtmGCqlCKyA==} + dev: false + /ramda/0.28.0: resolution: {integrity: sha512-9QnLuG/kPVgWvMQ4aODhsBUFKOUmnbUnsSXACv+NCQZcHbeb+v8Lodp8OVxtRULN1/xOyYLLaL6npE6dMq5QTA==} dev: false From 7bf42e65318c2cfe17c403b38d5df9e9ce18e9cf Mon Sep 17 00:00:00 2001 From: Sujith Date: Tue, 18 Oct 2022 16:38:24 +0530 Subject: [PATCH 03/33] fix: pnpm-lock Signed-off-by: Sujith --- pnpm-lock.yaml | 204 +++++++++++-------------------------------------- 1 file changed, 44 insertions(+), 160 deletions(-) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 52fbd4cbee6..1ab7367d529 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -225,16 +225,16 @@ importers: '@reactioncommerce/api-plugin-payments-example': link:../../packages/api-plugin-payments-example '@reactioncommerce/api-plugin-payments-stripe-sca': link:../../packages/api-plugin-payments-stripe-sca '@reactioncommerce/api-plugin-pricing-simple': link:../../packages/api-plugin-pricing-simple - '@reactioncommerce/api-plugin-products': 1.3.0_graphql@14.7.0 + '@reactioncommerce/api-plugin-products': link:../../packages/api-plugin-products '@reactioncommerce/api-plugin-settings': link:../../packages/api-plugin-settings '@reactioncommerce/api-plugin-shipments': link:../../packages/api-plugin-shipments '@reactioncommerce/api-plugin-shipments-flat-rate': link:../../packages/api-plugin-shipments-flat-rate '@reactioncommerce/api-plugin-shops': link:../../packages/api-plugin-shops '@reactioncommerce/api-plugin-simple-schema': link:../../packages/api-plugin-simple-schema '@reactioncommerce/api-plugin-sitemap-generator': link:../../packages/api-plugin-sitemap-generator - '@reactioncommerce/api-plugin-surcharges': 1.1.7_graphql@14.7.0 + '@reactioncommerce/api-plugin-surcharges': link:../../packages/api-plugin-surcharges '@reactioncommerce/api-plugin-system-information': link:../../packages/api-plugin-system-information - '@reactioncommerce/api-plugin-tags': 1.2.0_graphql@14.7.0 + '@reactioncommerce/api-plugin-tags': link:../../packages/api-plugin-tags '@reactioncommerce/api-plugin-taxes': link:../../packages/api-plugin-taxes '@reactioncommerce/api-plugin-taxes-flat-rate': link:../../packages/api-plugin-taxes-flat-rate '@reactioncommerce/api-plugin-translations': link:../../packages/api-plugin-translations @@ -485,7 +485,7 @@ importers: simpl-schema: ^1.12.0 dependencies: '@reactioncommerce/api-plugin-address-validation': link:../api-plugin-address-validation - '@reactioncommerce/api-plugin-tags': 1.2.0_graphql@15.8.0 + '@reactioncommerce/api-plugin-tags': link:../api-plugin-tags '@reactioncommerce/api-utils': link:../api-utils '@reactioncommerce/logger': link:../logger '@reactioncommerce/random': link:../random @@ -694,6 +694,46 @@ importers: '@reactioncommerce/api-plugin-carts': link:../api-plugin-carts '@reactioncommerce/data-factory': 1.0.1 + packages/api-plugin-fulfillment-method-shipping-flat-rate: + specifiers: + '@reactioncommerce/api-utils': ^1.16.9 + '@reactioncommerce/logger': ^1.1.3 + '@reactioncommerce/random': ~1.0.2 + '@reactioncommerce/reaction-error': ^1.0.1 + simpl-schema: ^1.12.2 + dependencies: + '@reactioncommerce/api-utils': link:../api-utils + '@reactioncommerce/logger': link:../logger + '@reactioncommerce/random': link:../random + '@reactioncommerce/reaction-error': link:../reaction-error + simpl-schema: 1.12.3 + + packages/api-plugin-fulfillment-method-shipping-ups: + specifiers: + '@reactioncommerce/api-utils': ^1.16.9 + '@reactioncommerce/reaction-error': ^1.0.1 + simpl-schema: ^1.12.2 + dependencies: + '@reactioncommerce/api-utils': link:../api-utils + '@reactioncommerce/reaction-error': link:../reaction-error + simpl-schema: 1.12.3 + + packages/api-plugin-fulfillment-type-shipping: + specifiers: + '@reactioncommerce/api-utils': ^1.16.9 + '@reactioncommerce/logger': ^1.1.3 + '@reactioncommerce/random': ~1.0.2 + '@reactioncommerce/reaction-error': ^1.0.1 + lodash: ^4.17.21 + simpl-schema: ^1.12.2 + dependencies: + '@reactioncommerce/api-utils': link:../api-utils + '@reactioncommerce/logger': link:../logger + '@reactioncommerce/random': link:../random + '@reactioncommerce/reaction-error': link:../reaction-error + lodash: 4.17.21 + simpl-schema: 1.12.3 + packages/api-plugin-i18n: specifiers: '@reactioncommerce/api-utils': ^1.16.5 @@ -4612,108 +4652,6 @@ packages: /@protobufjs/utf8/1.1.0: resolution: {integrity: sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw==} - /@reactioncommerce/api-plugin-products/1.3.0_graphql@14.7.0: - resolution: {integrity: sha512-utK/z9MXJ7qcASwvUlFuej5O5fIDUBi5kWg90am91H5p0wa8NKLXNsVzV/MC8oQDSq+/ljhtAhZQsPOBajxr1g==} - engines: {node: '>=14.18.1'} - dependencies: - '@reactioncommerce/api-utils': 1.16.9_graphql@14.7.0 - '@reactioncommerce/logger': 1.1.5 - '@reactioncommerce/random': 1.0.2 - '@reactioncommerce/reaction-error': 1.0.1 - lodash: 4.17.21 - simpl-schema: 1.12.3 - transitivePeerDependencies: - - graphql - dev: false - - /@reactioncommerce/api-plugin-surcharges/1.1.7_graphql@14.7.0: - resolution: {integrity: sha512-UdaWUmFEUHfCaOAGHs31S3yZGufmD7zO+GpOTlC4U310oQjr+CNQ0b2H8QxmSoUKnjgQfO9SN9bjiF5qUcOU6g==} - engines: {node: '>=14.18.1'} - dependencies: - '@reactioncommerce/api-utils': 1.16.9_graphql@14.7.0 - '@reactioncommerce/random': 1.0.2 - '@reactioncommerce/reaction-error': 1.0.1 - simpl-schema: 1.12.3 - transitivePeerDependencies: - - graphql - dev: false - - /@reactioncommerce/api-plugin-tags/1.2.0_graphql@14.7.0: - resolution: {integrity: sha512-6SgTt+AvGJs/ThRq8tMEiEF0HvWShJhzRNjInM+CJ/mz/1U9fDVRxhecwWI7iYG9gqs30XNFRhjgNdJxJVO0SA==} - engines: {node: '>=14.18.1'} - dependencies: - '@reactioncommerce/api-utils': 1.16.9_graphql@14.7.0 - '@reactioncommerce/data-factory': 1.0.1 - '@reactioncommerce/db-version-check': 1.0.0 - '@reactioncommerce/file-collections': 0.9.3 - '@reactioncommerce/random': 1.0.2 - '@reactioncommerce/reaction-error': 1.0.1 - lodash: 4.17.21 - simpl-schema: 1.10.2 - transitivePeerDependencies: - - encoding - - graphql - - supports-color - dev: false - - /@reactioncommerce/api-plugin-tags/1.2.0_graphql@15.8.0: - resolution: {integrity: sha512-6SgTt+AvGJs/ThRq8tMEiEF0HvWShJhzRNjInM+CJ/mz/1U9fDVRxhecwWI7iYG9gqs30XNFRhjgNdJxJVO0SA==} - engines: {node: '>=14.18.1'} - dependencies: - '@reactioncommerce/api-utils': 1.16.9_graphql@15.8.0 - '@reactioncommerce/data-factory': 1.0.1 - '@reactioncommerce/db-version-check': 1.0.0 - '@reactioncommerce/file-collections': 0.9.3 - '@reactioncommerce/random': 1.0.2 - '@reactioncommerce/reaction-error': 1.0.1 - lodash: 4.17.21 - simpl-schema: 1.10.2 - transitivePeerDependencies: - - encoding - - graphql - - supports-color - dev: false - - /@reactioncommerce/api-utils/1.16.9_graphql@14.7.0: - resolution: {integrity: sha512-9jB684K3GISSkkTeiMIniYiFgAtHL1AosSD2GPVpGSpFIUVbEL93e8owEadSIugmJt8yOb3m1+YdThW+kvXlzQ==} - engines: {node: '>=12.14.1'} - dependencies: - '@jest/globals': 26.6.2 - '@reactioncommerce/logger': 1.1.5 - '@reactioncommerce/random': 1.0.2 - '@reactioncommerce/reaction-error': 1.0.1 - accounting-js: 1.1.1 - callsite: 1.0.0 - envalid: 6.0.2 - graphql-fields: 2.0.3 - graphql-relay: 0.9.0_graphql@14.7.0 - lodash: 4.17.21 - ramda: 0.27.2 - transliteration: 2.3.5 - transitivePeerDependencies: - - graphql - dev: false - - /@reactioncommerce/api-utils/1.16.9_graphql@15.8.0: - resolution: {integrity: sha512-9jB684K3GISSkkTeiMIniYiFgAtHL1AosSD2GPVpGSpFIUVbEL93e8owEadSIugmJt8yOb3m1+YdThW+kvXlzQ==} - engines: {node: '>=12.14.1'} - dependencies: - '@jest/globals': 26.6.2 - '@reactioncommerce/logger': 1.1.5 - '@reactioncommerce/random': 1.0.2 - '@reactioncommerce/reaction-error': 1.0.1 - accounting-js: 1.1.1 - callsite: 1.0.0 - envalid: 6.0.2 - graphql-fields: 2.0.3 - graphql-relay: 0.9.0_graphql@15.8.0 - lodash: 4.17.21 - ramda: 0.27.2 - transliteration: 2.3.5 - transitivePeerDependencies: - - graphql - dev: false - /@reactioncommerce/babel-remove-es-create-require/1.0.0_@babel+core@7.19.0: resolution: {integrity: sha512-yR1vMj76hK5D9/VcXjFpk2OMoYpQdbvEgGcJ79cEzTL/jkFwIs53Zl6PNBjcC608P8qHl5eypEYRwG7CCGLzwQ==} peerDependencies: @@ -4744,10 +4682,6 @@ packages: transitivePeerDependencies: - supports-color - /@reactioncommerce/db-version-check/1.0.0: - resolution: {integrity: sha512-68ypfiCvdsmMaLmzNpXjN52SmgyHHO2IjLOscBbqBlvq/rFCVLLxV5/WkiFnedmNesFoXCfwYhyGWyjwaqicuw==} - dev: false - /@reactioncommerce/eslint-config/2.2.2_ihki3xsivcrciip6mkoznh225e: resolution: {integrity: sha512-E4BlfLmQBr5Sjt3LP9tAbgCKXZ2rQ4/Lpvle6khHUcuh33NcuBEInN+uStY6koPHAvnKgwn6MZ0LTYzVnKwSVA==} engines: {node: '>=8.1.0'} @@ -4773,43 +4707,11 @@ packages: eslint-plugin-you-dont-need-lodash-underscore: 6.12.0 dev: true - /@reactioncommerce/file-collections/0.9.3: - resolution: {integrity: sha512-8HLEjvlhtgpOAQYQeHA6UGYJfbYoSuUtQAyo0E0DUUpgeViXiWgQ+T4DIK3NbUinlvA4Ny8FDtZCNEg5eHCfMw==} - dependencies: - '@babel/runtime-corejs2': 7.19.0 - content-disposition: 0.5.4 - debug: 4.3.4 - extend: 3.0.2 - path-parser: 6.1.0 - query-string: 6.14.1 - tus-js-client: 1.8.0 - tus-node-server: 0.3.2 - transitivePeerDependencies: - - encoding - - supports-color - dev: false - - /@reactioncommerce/logger/1.1.5: - resolution: {integrity: sha512-fG6wohv9NoX6ZjNzs0YcFRFxHyndO4VitMCrbz+8HrnM7LNJtQRzrUcR58RptagYixZ4GyiHY6WhmIfrMpNvLw==} - dependencies: - bunyan: 1.8.15 - bunyan-format: 0.2.1 - node-loggly-bulk: 2.2.5 - dev: false - /@reactioncommerce/nodemailer/5.0.5: resolution: {integrity: sha512-u4ontTETlROmLglkMDyouMXlX62NXOGfOUAd75Ilk3W4tcsRjRXX+g5C5B4mBCCcJB0wHn1yh/a4pOYkn81vUQ==} engines: {node: '>=4.0.0'} dev: false - /@reactioncommerce/random/1.0.2: - resolution: {integrity: sha512-02eARZh6FQ45tEXXZUwnz90XWQasLpnV140CcuOl/L3dhEL4Emu2oCZemVhKvNb2JMmm5vOG2Y0CUb12xAx73w==} - dev: false - - /@reactioncommerce/reaction-error/1.0.1: - resolution: {integrity: sha512-l1HyDTFw4m1j8EPWNA/bbF2GZZaLYQjwmgniSjjM1cgnhfjCqeLywi501oPkmH8E84Jh0EH+4ADhNCcZ4m4gug==} - dev: false - /@sinclair/typebox/0.24.41: resolution: {integrity: sha512-TJCgQurls4FipFvHeC+gfAzb+GGstL0TDwYJKQVtTeSvJIznWzP7g3bAd5gEBlr8+bIxqnWS9VGVWREDhmE8jA==} dev: true @@ -9172,15 +9074,6 @@ packages: graphql: 14.7.0 dev: false - /graphql-relay/0.9.0_graphql@15.8.0: - resolution: {integrity: sha512-yNJLCqcjz0XpzpmmckRJCSK8a2ZLwTurwrQ09UyGftONh52PbrGpK1UO4yspvj0c7pC+jkN4ZUqVXG3LRrWkXQ==} - engines: {node: ^12.20.0 || ^14.15.0 || >= 15.9.0} - peerDependencies: - graphql: ^15.5.3 - dependencies: - graphql: 15.8.0 - dev: false - /graphql-schema-linter/3.0.0_graphql@16.6.0: resolution: {integrity: sha512-nXsyFvcqrD9/QWgimvJjvRBsUXHw8O7XoPLhpnGZd9eX6r2ZXsOBCGhTzF9ZrFtWosCBEfBGU2xpFUPe0qCHVg==} hasBin: true @@ -9232,11 +9125,6 @@ packages: dependencies: iterall: 1.3.0 - /graphql/15.8.0: - resolution: {integrity: sha512-5gghUc24tP9HRznNpV2+FIoq3xKkj5dTQqf4v0CpdPbFVwFkWoxOM+o+2OC9ZSvjEMTjfmG9QT+gcvggTwW1zw==} - engines: {node: '>= 10.x'} - dev: false - /graphql/16.6.0: resolution: {integrity: sha512-KPIBPDlW7NxrbT/eh4qPXz5FiFdL5UbaA0XUNz2Rp3Z3hqBSkbj0GVjwFDztsWVauZUWsbKHgMg++sk8UX0bkw==} engines: {node: ^12.22.0 || ^14.16.0 || ^16.0.0 || >=17.0.0} @@ -12531,10 +12419,6 @@ packages: engines: {node: '>=8'} dev: false - /ramda/0.27.2: - resolution: {integrity: sha512-SbiLPU40JuJniHexQSAgad32hfwd+DRUdwF2PlVuI5RZD0/vahUco7R8vD86J/tcEKKF9vZrUVwgtmGCqlCKyA==} - dev: false - /ramda/0.28.0: resolution: {integrity: sha512-9QnLuG/kPVgWvMQ4aODhsBUFKOUmnbUnsSXACv+NCQZcHbeb+v8Lodp8OVxtRULN1/xOyYLLaL6npE6dMq5QTA==} dev: false From 25aaed2abe60fa61288c616574c21e68eebb5cfa Mon Sep 17 00:00:00 2001 From: Sujith Date: Thu, 20 Oct 2022 01:05:55 +0530 Subject: [PATCH 04/33] fix: to generate function name in camel-case Signed-off-by: Sujith --- .../queries/getFulfillmentMethodsWithQuotes.js | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/packages/api-plugin-fulfillment/src/queries/getFulfillmentMethodsWithQuotes.js b/packages/api-plugin-fulfillment/src/queries/getFulfillmentMethodsWithQuotes.js index 02b1428e6c2..dd9759e4d7a 100644 --- a/packages/api-plugin-fulfillment/src/queries/getFulfillmentMethodsWithQuotes.js +++ b/packages/api-plugin-fulfillment/src/queries/getFulfillmentMethodsWithQuotes.js @@ -4,6 +4,21 @@ import extendCommonOrder from "../util/extendCommonOrder.js"; const logCtx = { name: "fulfillment", file: "getFulfillmentWithQuotes" }; +/** + * @name getTitleCaseOfWord + * @method + * @summary converts the given word to Title case + * @param {String} inputWord - input word + * @returns {String} return Title case of word + * @private + */ +function getTitleCaseOfWord(inputWord) { + let outWord = String(inputWord); + outWord = outWord.toLowerCase(); + outWord = outWord.charAt(0).toUpperCase() + outWord.slice(1); + return outWord; +} + /** * @name getFulfillmentMethodsWithQuotes * @method @@ -27,7 +42,8 @@ export default async function getFulfillmentMethodsWithQuotes(commonOrder, conte const fulfillmentTypeInGroup = commonOrder.fulfillmentType; if (!fulfillmentTypeInGroup) throw new ReactionError("not-found", "Fulfillment type not found in commonOrder"); - const functionTypesToCall = `getFulfillmentMethodsWithQuotes${fulfillmentTypeInGroup}`; + const fulfillmentTypeTitleCase = getTitleCaseOfWord(fulfillmentTypeInGroup); + const functionTypesToCall = `getFulfillmentMethodsWithQuotes${fulfillmentTypeTitleCase}`; const funcs = context.getFunctionsOfType(functionTypesToCall); if (!funcs || !Array.isArray(funcs) || !funcs.length) throw new ReactionError("not-found", `No methods for Fulfillment type ${fulfillmentTypeInGroup}`); From 7d0fea99dfdb21b56ce9aed4a828b563336eed70 Mon Sep 17 00:00:00 2001 From: Sujith Date: Mon, 24 Oct 2022 13:42:06 +0530 Subject: [PATCH 05/33] feat: createFFType test Signed-off-by: Sujith --- .../createFulfillmentType.test.js.snap | 3 + .../src/mutations/createFulfillmentType.js | 1 + .../mutations/createFulfillmentType.test.js | 69 +++++++++++++++++++ 3 files changed, 73 insertions(+) create mode 100644 packages/api-plugin-fulfillment/src/mutations/__snapshots__/createFulfillmentType.test.js.snap create mode 100644 packages/api-plugin-fulfillment/src/mutations/createFulfillmentType.test.js diff --git a/packages/api-plugin-fulfillment/src/mutations/__snapshots__/createFulfillmentType.test.js.snap b/packages/api-plugin-fulfillment/src/mutations/__snapshots__/createFulfillmentType.test.js.snap new file mode 100644 index 00000000000..828ff99c293 --- /dev/null +++ b/packages/api-plugin-fulfillment/src/mutations/__snapshots__/createFulfillmentType.test.js.snap @@ -0,0 +1,3 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`throws if required fields are not supplied 1`] = `"Enabled is required"`; diff --git a/packages/api-plugin-fulfillment/src/mutations/createFulfillmentType.js b/packages/api-plugin-fulfillment/src/mutations/createFulfillmentType.js index bd2bcadfcfa..50f92af8c19 100644 --- a/packages/api-plugin-fulfillment/src/mutations/createFulfillmentType.js +++ b/packages/api-plugin-fulfillment/src/mutations/createFulfillmentType.js @@ -11,6 +11,7 @@ import { fulfillmentTypeSchema } from "../simpleSchemas.js"; */ export default async function createFulfillmentType(context, input) { const cleanedInput = fulfillmentTypeSchema.clean(input); + if (!cleanedInput.provider) cleanedInput.provider = {}; cleanedInput.provider.name = cleanedInput.name; if (cleanedInput.method) { diff --git a/packages/api-plugin-fulfillment/src/mutations/createFulfillmentType.test.js b/packages/api-plugin-fulfillment/src/mutations/createFulfillmentType.test.js new file mode 100644 index 00000000000..c14d85fb3a8 --- /dev/null +++ b/packages/api-plugin-fulfillment/src/mutations/createFulfillmentType.test.js @@ -0,0 +1,69 @@ +import ReactionError from "@reactioncommerce/reaction-error"; +import mockCollection from "@reactioncommerce/api-utils/tests/mockCollection.js"; +import mockContext from "@reactioncommerce/api-utils/tests/mockContext.js"; +import createFulfillmentTypeMutation from "./createFulfillmentType.js"; + +mockContext.validatePermissions = jest.fn().mockName("validatePermissions"); +mockContext.collections.Fulfillment = mockCollection("Fulfillment"); +mockContext.validatePermissions.mockReturnValueOnce(Promise.resolve(null)); + +test("throws if required fields are not supplied", async () => { + const fulfillmentTypeInput = { + shopId: "SHOP_ID", + name: "fulfillmentType123" + }; + + await expect(createFulfillmentTypeMutation(mockContext, fulfillmentTypeInput)).rejects.toThrowErrorMatchingSnapshot(); +}); + +test("throws if the fulfillmentType added already exists", async () => { + mockContext.collections.Fulfillment.findOne.mockReturnValueOnce(Promise.resolve({ + _id: "fulfillment123", + name: "Default Shipping Provider", + shopId: "SHOP_ID", + provider: { + enabled: true, + label: "Shipping", + name: "shipping" + }, + fulfillmentType: "shipping" + })); + + const fulfillmentTypeInput = { + shopId: "SHOP_ID", + name: "fulfillmentType123", + fulfillmentType: "shipping", + provider: { + enabled: true, + label: "Shipping", + name: "shipping" + } + }; + const expectedError = new ReactionError("invalid-parameter", "Fulfillment Type already exists"); + await expect(createFulfillmentTypeMutation(mockContext, fulfillmentTypeInput)).rejects.toThrow(expectedError); +}); + +test("add a new fulfillment type", async () => { + mockContext.collections.Fulfillment.findOne.mockReturnValueOnce(Promise.resolve(undefined)); + mockContext.collections.Fulfillment.insertOne.mockReturnValueOnce(Promise.resolve({ result: { insertedCount: 1 } })); + + const fulfillmentTypeInput = { + shopId: "SHOP_ID", + name: "fulfillmentType123", + fulfillmentType: "shipping", + provider: { + enabled: true, + label: "Shipping", + name: "shipping" + } + }; + + const result = await createFulfillmentTypeMutation(mockContext, fulfillmentTypeInput); + + expect(result).toEqual({ + group: { + name: "fulfillmentType123", + fulfillmentType: "shipping" + } + }); +}); From a87d6f4c61e499d6234e944632b15aaa560aac2d Mon Sep 17 00:00:00 2001 From: Sujith Date: Mon, 24 Oct 2022 23:25:44 +0530 Subject: [PATCH 06/33] feat: ff-type tests Signed-off-by: Sujith --- ...lectFulfillmentOptionForGroup.test.js.snap | 5 + .../updateFulfillmentMethod.test.js.snap | 3 + ...ateFulfillmentOptionsForGroup.test.js.snap | 3 + .../updateFulfillmentType.test.js.snap | 3 + .../selectFulfillmentOptionForGroup.test.js | 67 +++++++ .../mutations/updateFulfillmentMethod.test.js | 136 ++++++++++++++ .../updateFulfillmentOptionsForGroup.test.js | 171 ++++++++++++++++++ .../src/mutations/updateFulfillmentType.js | 3 +- .../mutations/updateFulfillmentType.test.js | 44 +++++ 9 files changed, 433 insertions(+), 2 deletions(-) create mode 100644 packages/api-plugin-fulfillment/src/mutations/__snapshots__/selectFulfillmentOptionForGroup.test.js.snap create mode 100644 packages/api-plugin-fulfillment/src/mutations/__snapshots__/updateFulfillmentMethod.test.js.snap create mode 100644 packages/api-plugin-fulfillment/src/mutations/__snapshots__/updateFulfillmentOptionsForGroup.test.js.snap create mode 100644 packages/api-plugin-fulfillment/src/mutations/__snapshots__/updateFulfillmentType.test.js.snap create mode 100644 packages/api-plugin-fulfillment/src/mutations/selectFulfillmentOptionForGroup.test.js create mode 100644 packages/api-plugin-fulfillment/src/mutations/updateFulfillmentMethod.test.js create mode 100644 packages/api-plugin-fulfillment/src/mutations/updateFulfillmentOptionsForGroup.test.js create mode 100644 packages/api-plugin-fulfillment/src/mutations/updateFulfillmentType.test.js diff --git a/packages/api-plugin-fulfillment/src/mutations/__snapshots__/selectFulfillmentOptionForGroup.test.js.snap b/packages/api-plugin-fulfillment/src/mutations/__snapshots__/selectFulfillmentOptionForGroup.test.js.snap new file mode 100644 index 00000000000..c47469bdf28 --- /dev/null +++ b/packages/api-plugin-fulfillment/src/mutations/__snapshots__/selectFulfillmentOptionForGroup.test.js.snap @@ -0,0 +1,5 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`throws if there is no fulfillment group with the given ID 1`] = `"Fulfillment group with ID group2 not found in cart with ID cartId"`; + +exports[`throws if there is no fulfillment method with the given ID among the options 1`] = `"Fulfillment option with method ID invalid-method not found in cart with ID cartId"`; diff --git a/packages/api-plugin-fulfillment/src/mutations/__snapshots__/updateFulfillmentMethod.test.js.snap b/packages/api-plugin-fulfillment/src/mutations/__snapshots__/updateFulfillmentMethod.test.js.snap new file mode 100644 index 00000000000..d165ef0089d --- /dev/null +++ b/packages/api-plugin-fulfillment/src/mutations/__snapshots__/updateFulfillmentMethod.test.js.snap @@ -0,0 +1,3 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`throws if required fields are not supplied 1`] = `"Method ID is required"`; diff --git a/packages/api-plugin-fulfillment/src/mutations/__snapshots__/updateFulfillmentOptionsForGroup.test.js.snap b/packages/api-plugin-fulfillment/src/mutations/__snapshots__/updateFulfillmentOptionsForGroup.test.js.snap new file mode 100644 index 00000000000..45e9c989519 --- /dev/null +++ b/packages/api-plugin-fulfillment/src/mutations/__snapshots__/updateFulfillmentOptionsForGroup.test.js.snap @@ -0,0 +1,3 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`throws if there is no fulfillment group with the given ID 1`] = `"Fulfillment group with ID group2 not found in cart with ID cartId"`; diff --git a/packages/api-plugin-fulfillment/src/mutations/__snapshots__/updateFulfillmentType.test.js.snap b/packages/api-plugin-fulfillment/src/mutations/__snapshots__/updateFulfillmentType.test.js.snap new file mode 100644 index 00000000000..696b1da29dc --- /dev/null +++ b/packages/api-plugin-fulfillment/src/mutations/__snapshots__/updateFulfillmentType.test.js.snap @@ -0,0 +1,3 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`throws if required fields are not supplied 1`] = `"Name is required"`; diff --git a/packages/api-plugin-fulfillment/src/mutations/selectFulfillmentOptionForGroup.test.js b/packages/api-plugin-fulfillment/src/mutations/selectFulfillmentOptionForGroup.test.js new file mode 100644 index 00000000000..9068349c76d --- /dev/null +++ b/packages/api-plugin-fulfillment/src/mutations/selectFulfillmentOptionForGroup.test.js @@ -0,0 +1,67 @@ +import mockContext from "@reactioncommerce/api-utils/tests/mockContext.js"; +import selectFulfillmentOptionForGroup from "./selectFulfillmentOptionForGroup.js"; + +jest.mock("../util/getCartById", () => jest.fn().mockImplementation(() => Promise.resolve({ + _id: "cartId", + shipping: [{ + _id: "group1", + itemIds: ["123"], + shipmentQuotes: [{ + rate: 0, + method: { + _id: "valid-method" + } + }], + type: "shipping" + }] +}))); + +beforeAll(() => { + if (!mockContext.mutations.saveCart) { + mockContext.mutations.saveCart = jest.fn().mockName("context.mutations.saveCart").mockImplementation(async (_, cart) => cart); + } +}); + +test("selects an existing shipping method", async () => { + const result = await selectFulfillmentOptionForGroup(mockContext, { + cartId: "cartId", + fulfillmentGroupId: "group1", + fulfillmentMethodId: "valid-method" + }); + expect(result).toEqual({ + cart: { + _id: "cartId", + shipping: [{ + _id: "group1", + itemIds: ["123"], + shipmentQuotes: [{ + rate: 0, + method: { + _id: "valid-method" + } + }], + type: "shipping", + shipmentMethod: { + _id: "valid-method" + } + }], + updatedAt: jasmine.any(Date) + } + }); +}); + +test("throws if there is no fulfillment group with the given ID", async () => { + await expect(selectFulfillmentOptionForGroup(mockContext, { + cartId: "cartId", + fulfillmentGroupId: "group2", + fulfillmentMethodId: "valid-method" + })).rejects.toThrowErrorMatchingSnapshot(); +}); + +test("throws if there is no fulfillment method with the given ID among the options", async () => { + await expect(selectFulfillmentOptionForGroup(mockContext, { + cartId: "cartId", + fulfillmentGroupId: "group1", + fulfillmentMethodId: "invalid-method" + })).rejects.toThrowErrorMatchingSnapshot(); +}); diff --git a/packages/api-plugin-fulfillment/src/mutations/updateFulfillmentMethod.test.js b/packages/api-plugin-fulfillment/src/mutations/updateFulfillmentMethod.test.js new file mode 100644 index 00000000000..0a230020809 --- /dev/null +++ b/packages/api-plugin-fulfillment/src/mutations/updateFulfillmentMethod.test.js @@ -0,0 +1,136 @@ +import ReactionError from "@reactioncommerce/reaction-error"; +import mockCollection from "@reactioncommerce/api-utils/tests/mockCollection.js"; +import mockContext from "@reactioncommerce/api-utils/tests/mockContext.js"; +import updateFulfillmentMethodMutation from "./updateFulfillmentMethod.js"; + +mockContext.validatePermissions = jest.fn().mockName("validatePermissions"); +mockContext.collections.Fulfillment = mockCollection("Fulfillment"); +mockContext.validatePermissions.mockReturnValueOnce(Promise.resolve(null)); + +test("throws if required fields are not supplied", async () => { + const fulfillmentMethodInput = { + fulfillmentTypeId: "fulfillmentType01", + shopId: "SHOP_ID", + method: { + fulfillmentTypes: ["Shipping"], + group: "Ground", + handling: 99, + enabled: true, + label: "ups", + name: "ups", + rate: 99 + } + }; + + await expect(updateFulfillmentMethodMutation(mockContext, fulfillmentMethodInput)).rejects.toThrowErrorMatchingSnapshot(); +}); + +test("throws if the fulfillmentType does not exists", async () => { + mockContext.collections.Fulfillment.findOne.mockReturnValueOnce(Promise.resolve(undefined)); + + const fulfillmentMethodInput = { + fulfillmentTypeId: "fulfillmentType01", + shopId: "SHOP_ID", + methodId: "fulfillmentMethodId", + method: { + fulfillmentTypes: ["Shipping"], + group: "Ground", + handling: 99, + enabled: true, + label: "ups", + name: "ups", + rate: 99 + } + }; + const expectedError = new ReactionError("server-error", "Fulfillment Method does not exist"); + await expect(updateFulfillmentMethodMutation(mockContext, fulfillmentMethodInput)).rejects.toThrow(expectedError); +}); + +test("throws if the fulfillmentMethod does not exists", async () => { + mockContext.collections.Fulfillment.findOne.mockReturnValueOnce(Promise.resolve({ + fulfillmentTypeId: "fulfillmentType01", + shopId: "SHOP_ID", + methods: [{ + _id: "fulfillmentMethodId01", + fulfillmentTypes: ["Shipping"], + group: "Ground", + handling: 99, + enabled: true, + label: "ups", + name: "ups", + rate: 99 + }] + })); + + const fulfillmentMethodInput = { + fulfillmentTypeId: "fulfillmentType01", + shopId: "SHOP_ID", + methodId: "fulfillmentMethodId02", + method: { + fulfillmentTypes: ["Shipping"], + group: "Ground", + handling: 99, + enabled: true, + label: "ups", + name: "ups", + rate: 99 + } + }; + const expectedError = new ReactionError("server-error", "Fulfillment Method does not exist"); + await expect(updateFulfillmentMethodMutation(mockContext, fulfillmentMethodInput)).rejects.toThrow(expectedError); +}); + +test("should update an existing fulfillment method", async () => { + mockContext.collections.Fulfillment.updateOne.mockReturnValueOnce(Promise.resolve({ result: { matchedCount: 1 } })); + mockContext.collections.Fulfillment.findOne.mockReturnValueOnce(Promise.resolve({ + fulfillmentTypeId: "fulfillmentType01", + shopId: "SHOP_ID", + fulfillmentType: "Shipping", + methods: [{ + _id: "fulfillmentMethodId01", + fulfillmentTypes: ["Shipping"], + group: "Ground", + handling: 99, + enabled: true, + label: "ups", + name: "ups", + rate: 99, + fulfillmentMethod: "ups" + }] + })); + + const fulfillmentMethodInput = { + fulfillmentTypeId: "fulfillmentType01", + shopId: "SHOP_ID", + methodId: "fulfillmentMethodId01", + method: { + fulfillmentTypes: ["Shipping"], + group: "Ground", + handling: 99, + enabled: true, + label: "ups", + name: "ups", + rate: 99, + fulfillmentMethod: "ups" + } + }; + + const expectedOutput = { + group: { + _id: "fulfillmentMethodId01", + enabled: true, + fulfillmentMethod: "ups", + fulfillmentType: ["Shipping"], + fulfillmentTypes: ["Shipping"], + group: "Ground", + handling: 99, + label: "ups", + name: "ups", + rate: 99 + } + }; + + const result = await updateFulfillmentMethodMutation(mockContext, fulfillmentMethodInput); + + expect(result).toEqual(expectedOutput); +}); diff --git a/packages/api-plugin-fulfillment/src/mutations/updateFulfillmentOptionsForGroup.test.js b/packages/api-plugin-fulfillment/src/mutations/updateFulfillmentOptionsForGroup.test.js new file mode 100644 index 00000000000..2dbbc8a9b0f --- /dev/null +++ b/packages/api-plugin-fulfillment/src/mutations/updateFulfillmentOptionsForGroup.test.js @@ -0,0 +1,171 @@ +import mockContext from "@reactioncommerce/api-utils/tests/mockContext.js"; +import Factory from "../tests/factory.js"; +import updateFulfillmentOptionsForGroup from "./updateFulfillmentOptionsForGroup.js"; + +jest.mock("../util/getCartById", () => jest.fn().mockImplementation(() => Promise.resolve({ + _id: "cartId", + items: [{ + _id: "123", + price: { + amount: 19.99 + }, + priceWhenAdded: { + amount: 19.99 + }, + subtotal: { + amount: 19.99 + } + }], + shipping: [{ + _id: "group1", + itemIds: ["123"], + type: "shipping" + }] +}))); + +const fakeCart = Factory.Cart.makeOne(); +const fakeQuote = Factory.ShipmentQuote.makeOne(); +const mockGetFulfillmentMethodsWithQuotes = jest.fn().mockName("getFulfillmentMethodsWithQuotes"); +const mockGetCommonOrderForCartGroup = jest.fn().mockName("getCommonOrderForCartGroup"); + +beforeAll(() => { + mockContext.queries = { + getFulfillmentMethodsWithQuotes: mockGetFulfillmentMethodsWithQuotes, + getCommonOrderForCartGroup: mockGetCommonOrderForCartGroup + }; + if (!mockContext.mutations.saveCart) { + mockContext.mutations.saveCart = jest.fn().mockName("context.mutations.saveCart").mockImplementation(async (_, cart) => cart); + } + if (!mockContext.mutations.removeMissingItemsFromCart) { + mockContext.mutations.removeMissingItemsFromCart = jest.fn().mockName("context.mutations.removeMissingItemsFromCart"); + } +}); + +beforeEach(() => { + mockGetFulfillmentMethodsWithQuotes.mockClear(); +}); + +test("updates cart properly for empty rates", async () => { + mockGetFulfillmentMethodsWithQuotes.mockReturnValueOnce(Promise.resolve([])); + mockContext.collections.Cart.findOne.mockReturnValueOnce(Promise.resolve(fakeCart)); + + const result = await updateFulfillmentOptionsForGroup(mockContext, { + cartId: "cartId", + fulfillmentGroupId: "group1" + }); + + expect(result).toEqual({ + cart: { + _id: "cartId", + items: [{ + _id: "123", + price: { + amount: 19.99 + }, + priceWhenAdded: { + amount: 19.99 + }, + subtotal: { + amount: 19.99 + } + }], + shipping: [{ + _id: "group1", + itemIds: ["123"], + type: "shipping", + shipmentQuotes: [], + shipmentQuotesQueryStatus: { requestStatus: "pending" } + }], + updatedAt: jasmine.any(Date) + } + }); +}); + +test("updates cart properly for error rates", async () => { + mockGetFulfillmentMethodsWithQuotes.mockReturnValueOnce(Promise.resolve([{ + requestStatus: "error", + shippingProvider: "all", + message: "All requests for shipping methods failed." + }])); + + const result = await updateFulfillmentOptionsForGroup(mockContext, { + cartId: "cartId", + fulfillmentGroupId: "group1" + }); + + expect(result).toEqual({ + cart: { + _id: "cartId", + items: [{ + _id: "123", + price: { + amount: 19.99 + }, + priceWhenAdded: { + amount: 19.99 + }, + subtotal: { + amount: 19.99 + } + }], + shipping: [{ + _id: "group1", + itemIds: ["123"], + type: "shipping", + shipmentQuotes: [], + shipmentQuotesQueryStatus: { + requestStatus: "error", + shippingProvider: "all", + message: "All requests for shipping methods failed." + } + }], + updatedAt: jasmine.any(Date) + } + }); +}); + +test("updates cart properly for success rates", async () => { + mockGetFulfillmentMethodsWithQuotes.mockReturnValueOnce(Promise.resolve([fakeQuote])); + mockContext.collections.Cart.findOne.mockReturnValueOnce(Promise.resolve(fakeCart)); + + const result = await updateFulfillmentOptionsForGroup(mockContext, { + cartId: "cartId", + fulfillmentGroupId: "group1" + }); + + expect(result).toEqual({ + cart: { + _id: "cartId", + items: [{ + _id: "123", + price: { + amount: 19.99 + }, + priceWhenAdded: { + amount: 19.99 + }, + subtotal: { + amount: 19.99 + } + }], + shipping: [{ + _id: "group1", + itemIds: ["123"], + type: "shipping", + shipmentQuotes: [fakeQuote], + shipmentQuotesQueryStatus: { + requestStatus: "success", + numOfShippingMethodsFound: 1 + } + }], + updatedAt: jasmine.any(Date) + } + }); +}); + +test("throws if there is no fulfillment group with the given ID", async () => { + await expect(updateFulfillmentOptionsForGroup(mockContext, { + cartId: "cartId", + fulfillmentGroupId: "group2" + })).rejects.toThrowErrorMatchingSnapshot(); +}); diff --git a/packages/api-plugin-fulfillment/src/mutations/updateFulfillmentType.js b/packages/api-plugin-fulfillment/src/mutations/updateFulfillmentType.js index 089c91e0b4c..8282e3dbb00 100644 --- a/packages/api-plugin-fulfillment/src/mutations/updateFulfillmentType.js +++ b/packages/api-plugin-fulfillment/src/mutations/updateFulfillmentType.js @@ -28,8 +28,7 @@ export default async function updateFulfillmentType(context, input) { inputSchema.validate(cleanedInput); const { fulfillmentGroupId, shopId, name, enabled, label, displayMessageType } = cleanedInput; - const { collections } = context; - const { Fulfillment } = collections; + const { collections: { Fulfillment } } = context; if (!shopId) throw new ReactionError("invalid-parameter", "Shop ID to be updated not provided"); if (!fulfillmentGroupId) throw new ReactionError("invalid-parameter", "FulfillmentType ID to be updated not provided"); diff --git a/packages/api-plugin-fulfillment/src/mutations/updateFulfillmentType.test.js b/packages/api-plugin-fulfillment/src/mutations/updateFulfillmentType.test.js new file mode 100644 index 00000000000..30402070311 --- /dev/null +++ b/packages/api-plugin-fulfillment/src/mutations/updateFulfillmentType.test.js @@ -0,0 +1,44 @@ +import mockCollection from "@reactioncommerce/api-utils/tests/mockCollection.js"; +import mockContext from "@reactioncommerce/api-utils/tests/mockContext.js"; +import updateFulfillmentTypeMutation from "./updateFulfillmentType.js"; + +mockContext.validatePermissions = jest.fn().mockName("validatePermissions"); +mockContext.collections.Fulfillment = mockCollection("Fulfillment"); +mockContext.validatePermissions.mockReturnValueOnce(Promise.resolve(null)); + +test("throws if required fields are not supplied", async () => { + const fulfillmentTypeInput = { + fulfillmentGroupId: "fulfillmentGroup01", + shopId: "SHOP_ID", + label: "Shipping" + }; + + await expect(updateFulfillmentTypeMutation(mockContext, fulfillmentTypeInput)).rejects.toThrowErrorMatchingSnapshot(); +}); + + +test("should update an existing fulfillment type", async () => { + mockContext.collections.Fulfillment.updateOne.mockReturnValueOnce(Promise.resolve({ result: { matchedCount: 1 } })); + + const fulfillmentTypeInput = { + fulfillmentGroupId: "fulfillmentGroup01", + shopId: "SHOP_ID", + name: "fulfillmentType123", + enabled: false, + label: "Shipping" + }; + + const expectedOutput = { + fulfillmentGroupId: "fulfillmentGroup01", + shopId: "SHOP_ID", + name: "fulfillmentType123", + enabled: false, + label: "Shipping" + }; + + const result = await updateFulfillmentTypeMutation(mockContext, fulfillmentTypeInput); + + expect(result).toEqual({ + group: expectedOutput + }); +}); From cb3e9a301ef8508ec9b7e0a7fdca5b495f38b5ac Mon Sep 17 00:00:00 2001 From: Sujith Date: Wed, 26 Oct 2022 15:57:47 +0530 Subject: [PATCH 07/33] fix: review comment fixes Signed-off-by: Sujith --- packages/api-plugin-fulfillment/.gitignore | 61 ----------------- packages/api-plugin-fulfillment/README.md | 67 +------------------ packages/api-plugin-fulfillment/src/index.js | 10 ++- .../src/mutations/createFulfillmentMethod.js | 17 +++-- .../src/mutations/createFulfillmentType.js | 23 ++++--- .../src/mutations/updateFulfillmentMethod.js | 15 ++--- .../updateFulfillmentOptionsForGroup.js | 3 +- .../src/mutations/updateFulfillmentType.js | 8 ++- .../api-plugin-fulfillment/src/preStartup.js | 2 +- .../getFulfillmentMethodsWithQuotes.js | 2 +- .../src/queries/getFulfillmentType.js | 5 +- .../src/registration.js | 6 +- .../resolvers/Query/getFulfillmentTypes.js | 2 +- .../src/resolvers/index.js | 5 -- .../src/simpleSchemas.js | 40 ++++++++++- .../api-plugin-fulfillment/src/startup.js | 3 +- 16 files changed, 94 insertions(+), 175 deletions(-) delete mode 100644 packages/api-plugin-fulfillment/.gitignore diff --git a/packages/api-plugin-fulfillment/.gitignore b/packages/api-plugin-fulfillment/.gitignore deleted file mode 100644 index ad46b30886f..00000000000 --- a/packages/api-plugin-fulfillment/.gitignore +++ /dev/null @@ -1,61 +0,0 @@ -# Logs -logs -*.log -npm-debug.log* -yarn-debug.log* -yarn-error.log* - -# Runtime data -pids -*.pid -*.seed -*.pid.lock - -# Directory for instrumented libs generated by jscoverage/JSCover -lib-cov - -# Coverage directory used by tools like istanbul -coverage - -# nyc test coverage -.nyc_output - -# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) -.grunt - -# Bower dependency directory (https://bower.io/) -bower_components - -# node-waf configuration -.lock-wscript - -# Compiled binary addons (https://nodejs.org/api/addons.html) -build/Release - -# Dependency directories -node_modules/ -jspm_packages/ - -# TypeScript v1 declaration files -typings/ - -# Optional npm cache directory -.npm - -# Optional eslint cache -.eslintcache - -# Optional REPL history -.node_repl_history - -# Output of 'npm pack' -*.tgz - -# Yarn Integrity file -.yarn-integrity - -# dotenv environment variables file -.env - -# next.js build output -.next diff --git a/packages/api-plugin-fulfillment/README.md b/packages/api-plugin-fulfillment/README.md index 53aaf9acead..22ab3b26a29 100644 --- a/packages/api-plugin-fulfillment/README.md +++ b/packages/api-plugin-fulfillment/README.md @@ -1,68 +1,5 @@ # api-plugin-fulfillment -[![npm (scoped)](https://img.shields.io/npm/v/@reactioncommerce/api-plugin-fulfillment.svg)](https://www.npmjs.com/package/@reactioncommerce/api-plugin-fulfillment) -[![CircleCI](https://circleci.com/gh/reactioncommerce/api-plugin-fulfillment.svg?style=svg)](https://circleci.com/gh/reactioncommerce/api-plugin-fulfillment) - - -## Summary - -This plugin provides the base for all fulfillment types and the fulfillment methods under each type. - -## Included in this fulfillment plugin -### `src/` - -The `src` folder is where all the plugin files resides. - -### `.gitignore` - -A basic `gitignore` file -### `babel.config.cjs` - -If your plugin includes linting and tests, this file is required to allow esmodules to run correctly. - -### `jest.config.cjs` - -If your plugin includes tests, this file is required to allow esmodules to run correctly. You'll need to update the `transformIgnorePatterns` and `moduleNameMapper` sections to include any esmodule `npm` packages used in your plugin. - -### `License.md` - -If your plugin uses `Apache 2` licensing, you can leave this file as-is. If another type of licensing is used, you need to update this file, and the README, accordingly. - -### `package.json` - -The provided `package.json` is set up to install all needed packages and config for linting, testing, and semantic-release. You'll need to update the `name`, `description`, and add any new dependencies your plugin files use. - -### `index.js` - -The entrypoint file for your npm package, will most likely just export your plugin registration from the `src` folder. - -## Developer Certificate of Origin -We use the [Developer Certificate of Origin (DCO)](https://developercertificate.org/) in lieu of a Contributor License Agreement for all contributions to Reaction Commerce open source projects. We request that contributors agree to the terms of the DCO and indicate that agreement by signing all commits made to Reaction Commerce projects by adding a line with your name and email address to every Git commit message contributed: -``` -Signed-off-by: Jane Doe -``` - -You can sign your commit automatically with Git by using `git commit -s` if you have your `user.name` and `user.email` set as part of your Git configuration. - -We ask that you use your real name (please no anonymous contributions or pseudonyms). By signing your commit you are certifying that you have the right have the right to submit it under the open source license used by that particular Reaction Commerce project. You must use your real name (no pseudonyms or anonymous contributions are allowed.) - -We use the [Probot DCO GitHub app](https://github.com/apps/dco) to check for DCO signoffs of every commit. - -If you forget to sign your commits, the DCO bot will remind you and give you detailed instructions for how to amend your commits to add a signature. - -## License - - Copyright 2020 Reaction Commerce - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. +This is the base fulfillment plugin which would enable other fulfillment types (like shipping, pickup, digital) and fulfillment-methods (like shipping, store, download etc) to be introduced via their own separate plugins. This plugin provides the common functionalities required for implementing fulfillment. +Each of the newly introduced fulfillment type plugin would need to have the specific fulfillment methods also to be added as separate plugins. Example, fulfillment type 'pickup' could have fulfillment methods like 'store pickup' and 'curb-side pickup' and each of them would be separate plugins. \ No newline at end of file diff --git a/packages/api-plugin-fulfillment/src/index.js b/packages/api-plugin-fulfillment/src/index.js index 2a006b76fac..079be3a337b 100644 --- a/packages/api-plugin-fulfillment/src/index.js +++ b/packages/api-plugin-fulfillment/src/index.js @@ -6,7 +6,7 @@ import schemas from "./schemas/index.js"; import startup from "./startup.js"; import preStartup from "./preStartup.js"; import { MethodEmptyData } from "./simpleSchemas.js"; -import { allRegisteredFulfillmentTypes, registerPluginHandlerForFulfillmentTypes } from "./registration.js"; +import { fulfillment, registerPluginHandlerForFulfillmentTypes } from "./registration.js"; const require = createRequire(import.meta.url); const pkg = require("../package.json"); @@ -31,10 +31,8 @@ export default async function register(app) { Fulfillment: { name: "Fulfillment", indexes: [ - // Create indexes. We set specific names for backwards compatibility - // with indexes created by the aldeed:schema-index Meteor package. - [{ name: 1 }, { name: "c2_name" }], - [{ shopId: 1 }, { name: "c2_shopId" }] + [{ name: 1 }], + [{ shopId: 1 }] ] } }, @@ -50,7 +48,7 @@ export default async function register(app) { startup: [startup] }, contextAdditions: { - allRegisteredFulfillmentTypes + fulfillment }, simpleSchemas: { MethodEmptyData diff --git a/packages/api-plugin-fulfillment/src/mutations/createFulfillmentMethod.js b/packages/api-plugin-fulfillment/src/mutations/createFulfillmentMethod.js index b0985d03a74..abd769e76b2 100644 --- a/packages/api-plugin-fulfillment/src/mutations/createFulfillmentMethod.js +++ b/packages/api-plugin-fulfillment/src/mutations/createFulfillmentMethod.js @@ -13,7 +13,16 @@ const inputSchema = new SimpleSchema({ * @method createFulfillmentMethodMutation * @summary Creates a fulfillment method * @param {Object} context - an object containing the per-request state - * @param {Object} input - Input (see SimpleSchema) + * @param {Object} input - Input + * @param {Number} cost - optional cost + * @param {String[]} fulfillmentTypes - fulfillment type retained for backward compatibility + * @param {String} group - Group to which fulfillment method belong + * @param {Number} handling - handling charges + * @param {Boolean} enabled - status of fulfillment method + * @param {String} label - displayed on the UI + * @param {String} fulfillmentMethod - used by application, not user editable + * @param {String} displayMessageMethod - used to display additional message on UI specific to ff-method + * @param {Number} rate - ratefor themethod * @returns {Promise} An object with a `method` property containing the created method */ export default async function createFulfillmentMethodMutation(context, input) { @@ -33,15 +42,11 @@ export default async function createFulfillmentMethodMutation(context, input) { const ffTypeMethodRecord = await Fulfillment.findOne({ _id: fulfillmentTypeId, shopId, - methods: { $elemMatch: { name: method.name, fulfillmentMethod: method.fulfillmentMethod } } + methods: { $elemMatch: { fulfillmentMethod: method.fulfillmentMethod } } }); if (ffTypeMethodRecord) throw new ReactionError("server-error", "Fulfillment Method already exists"); method._id = Random.id(); - // MongoDB schema still uses `enabled` rather than `isEnabled` - // method.enabled = method.isEnabled; - // delete method.isEnabled; - method.fulfillmentTypes = [ffTypeRecord.fulfillmentType]; const { matchedCount } = await Fulfillment.updateOne({ diff --git a/packages/api-plugin-fulfillment/src/mutations/createFulfillmentType.js b/packages/api-plugin-fulfillment/src/mutations/createFulfillmentType.js index 50f92af8c19..c0f8ffff0c6 100644 --- a/packages/api-plugin-fulfillment/src/mutations/createFulfillmentType.js +++ b/packages/api-plugin-fulfillment/src/mutations/createFulfillmentType.js @@ -1,16 +1,22 @@ import Random from "@reactioncommerce/random"; import ReactionError from "@reactioncommerce/reaction-error"; -import { fulfillmentTypeSchema } from "../simpleSchemas.js"; +import { FulfillmentTypeSchema } from "../simpleSchemas.js"; /** * @method createFulfillmentType - * @summary updates the selected fulfillment type + * @summary Creates a new fulfillment type * @param {Object} context - an object containing the per-request state - * @param {Object} input - Input (see SimpleSchema) + * @param {Object} input - Input object + * @param {String} input.name - name of fulfillment type + * @param {String} input.shopId - Shop Id + * @param {ProviderSchema} input.provider - Provider details + * @param {String} input.fulfillmentType - fulfillment type + * @param {String} input.displayMessageType - displayMessage for fulfillment type + * @param {FulfillmentMethodSchema[]} input.methods - ff-method array * @returns {Promise} An object with the updated type */ export default async function createFulfillmentType(context, input) { - const cleanedInput = fulfillmentTypeSchema.clean(input); + const cleanedInput = FulfillmentTypeSchema.clean(input); if (!cleanedInput.provider) cleanedInput.provider = {}; cleanedInput.provider.name = cleanedInput.name; @@ -18,13 +24,12 @@ export default async function createFulfillmentType(context, input) { cleanedInput.method._id = Random.id(); cleanedInput.method.fulfillmentTypes = [cleanedInput.fulfillmentType]; } - fulfillmentTypeSchema.validate(cleanedInput); + FulfillmentTypeSchema.validate(cleanedInput); - const { collections } = context; - const { Fulfillment } = collections; - const { shopId, name, fulfillmentType } = cleanedInput; + const { collections: { Fulfillment } } = context; + const { shopId, fulfillmentType } = cleanedInput; - const ffTypeRecord = await Fulfillment.findOne({ name, shopId, fulfillmentType }); + const ffTypeRecord = await Fulfillment.findOne({ shopId, fulfillmentType }); if (ffTypeRecord) throw new ReactionError("invalid-parameter", "Fulfillment Type already exists"); await context.validatePermissions("reaction:legacy:fulfillmentTypes", "create", { shopId }); diff --git a/packages/api-plugin-fulfillment/src/mutations/updateFulfillmentMethod.js b/packages/api-plugin-fulfillment/src/mutations/updateFulfillmentMethod.js index 152c7a215f1..45308082112 100644 --- a/packages/api-plugin-fulfillment/src/mutations/updateFulfillmentMethod.js +++ b/packages/api-plugin-fulfillment/src/mutations/updateFulfillmentMethod.js @@ -11,9 +11,13 @@ const inputSchema = new SimpleSchema({ /** * @method updateFulfillmentMethodMutation - * @summary updates a flat rate fulfillment method + * @summary updates Fulfillment method * @param {Object} context - an object containing the per-request state - * @param {Object} input - Input (see SimpleSchema) + * @param {Object} input - Input object + * @param {FulfillmentMethodSchema} input.method - fulfillment method object + * @param {String} input.fulfillmentTypeId - id of fulfillment type + * @param {String} input.methodId - ff-method Id + * @param {String} input.shopId - Shop Id * @returns {Promise} An object with a `method` property containing the updated method */ export default async function updateFulfillmentMethodMutation(context, input) { @@ -21,8 +25,7 @@ export default async function updateFulfillmentMethodMutation(context, input) { inputSchema.validate(cleanedInput); const { method: inputMethod, methodId, fulfillmentTypeId, shopId } = cleanedInput; - const { collections } = context; - const { Fulfillment } = collections; + const { collections: { Fulfillment } } = context; const method = { ...inputMethod }; if (!fulfillmentTypeId) throw new ReactionError("invalid-parameter", "Fulfillment Type ID to be updated not provided"); @@ -30,10 +33,6 @@ export default async function updateFulfillmentMethodMutation(context, input) { await context.validatePermissions(`reaction:legacy:fulfillmentMethods:${methodId}`, "update", { shopId }); - // MongoDB schema still uses `enabled` rather than `isEnabled` - // method.enabled = method.isEnabled; - // delete method.isEnabled; - const ffTypeMethodRecord = await Fulfillment.findOne({ "_id": fulfillmentTypeId, shopId, diff --git a/packages/api-plugin-fulfillment/src/mutations/updateFulfillmentOptionsForGroup.js b/packages/api-plugin-fulfillment/src/mutations/updateFulfillmentOptionsForGroup.js index ada20aa6637..d008c1a3e5c 100644 --- a/packages/api-plugin-fulfillment/src/mutations/updateFulfillmentOptionsForGroup.js +++ b/packages/api-plugin-fulfillment/src/mutations/updateFulfillmentOptionsForGroup.js @@ -13,7 +13,8 @@ const inputSchema = new SimpleSchema({ }); /** - * @name getShipmentQuotesQueryStatus + * @method getShipmentQuotesQueryStatus + * @summary returns formatted quotes and queryStatus using the rates as input * @param {Array} rates Rate array * @returns {Object} An object with `shipmentQuotes` and `shipmentQuotesQueryStatus` on it * @private diff --git a/packages/api-plugin-fulfillment/src/mutations/updateFulfillmentType.js b/packages/api-plugin-fulfillment/src/mutations/updateFulfillmentType.js index 8282e3dbb00..41c21aef41a 100644 --- a/packages/api-plugin-fulfillment/src/mutations/updateFulfillmentType.js +++ b/packages/api-plugin-fulfillment/src/mutations/updateFulfillmentType.js @@ -20,7 +20,13 @@ const inputSchema = new SimpleSchema({ * @method updateFulfillmentType * @summary updates the selected fulfillment type * @param {Object} context - an object containing the per-request state - * @param {Object} input - Input (see SimpleSchema) + * @param {Object} input - Input object + * @param {String} input.fulfillmentGroupId - fulfillment tpe id of group + * @param {String} input.shopId - Shop Id + * @param {String} input.name - name of fulfillment type + * @param {Boolean} input.enabled - status of ff-type + * @param {String} input.label - label of ff-type + * @param {String} input.displayMessageType - optional display message for UI * @returns {Promise} An object with the updated type */ export default async function updateFulfillmentType(context, input) { diff --git a/packages/api-plugin-fulfillment/src/preStartup.js b/packages/api-plugin-fulfillment/src/preStartup.js index a2f7d44ccf5..e65d3743063 100644 --- a/packages/api-plugin-fulfillment/src/preStartup.js +++ b/packages/api-plugin-fulfillment/src/preStartup.js @@ -10,7 +10,7 @@ const logCtx = { name: "fulfillment", file: "preStartup" }; * @returns {undefined} */ export default async function fulfillmentPreStartup(context) { - const allFulfillmentTypesArray = context.allRegisteredFulfillmentTypes?.registeredFulfillmentTypes; + const allFulfillmentTypesArray = context.fulfillment?.registeredFulfillmentTypes; if (!allFulfillmentTypesArray || allFulfillmentTypesArray.length === 0) { Logger.warn(logCtx, "No fulfillment types available"); diff --git a/packages/api-plugin-fulfillment/src/queries/getFulfillmentMethodsWithQuotes.js b/packages/api-plugin-fulfillment/src/queries/getFulfillmentMethodsWithQuotes.js index dd9759e4d7a..a380cbf744c 100644 --- a/packages/api-plugin-fulfillment/src/queries/getFulfillmentMethodsWithQuotes.js +++ b/packages/api-plugin-fulfillment/src/queries/getFulfillmentMethodsWithQuotes.js @@ -2,7 +2,7 @@ import Logger from "@reactioncommerce/logger"; import ReactionError from "@reactioncommerce/reaction-error"; import extendCommonOrder from "../util/extendCommonOrder.js"; -const logCtx = { name: "fulfillment", file: "getFulfillmentWithQuotes" }; +const logCtx = { name: "fulfillment", file: "getFulfillmentMethodsWithQuotes" }; /** * @name getTitleCaseOfWord diff --git a/packages/api-plugin-fulfillment/src/queries/getFulfillmentType.js b/packages/api-plugin-fulfillment/src/queries/getFulfillmentType.js index c2b0596efc2..81ce44b0276 100644 --- a/packages/api-plugin-fulfillment/src/queries/getFulfillmentType.js +++ b/packages/api-plugin-fulfillment/src/queries/getFulfillmentType.js @@ -7,11 +7,10 @@ * @param {Object} input - Request input * @param {String} input.fulfillmentTypeId - The fulfillment type id * @param {String} input.shopId - The shop id of the fulfillment type - * @returns {Promise} Mongo cursor + * @returns {Promise} Object */ export default async function getFulfillmentType(context, input) { - const { collections } = context; - const { Fulfillment } = collections; + const { collections: { Fulfillment } } = context; const { fulfillmentTypeId, shopId } = input; await context.validatePermissions("reaction:legacy:fulfillmentTypes", "read", { shopId }); diff --git a/packages/api-plugin-fulfillment/src/registration.js b/packages/api-plugin-fulfillment/src/registration.js index 9645f27e9eb..209a52c2b02 100644 --- a/packages/api-plugin-fulfillment/src/registration.js +++ b/packages/api-plugin-fulfillment/src/registration.js @@ -9,7 +9,7 @@ const FulfillmentTypeDeclaration = new SimpleSchema({ } }); -export const allRegisteredFulfillmentTypes = { +export const fulfillment = { registeredFulfillmentTypes: ["undecided"] }; @@ -20,8 +20,8 @@ export const allRegisteredFulfillmentTypes = { */ export function registerPluginHandlerForFulfillmentTypes({ registeredFulfillmentTypes }) { if (registeredFulfillmentTypes) { - allRegisteredFulfillmentTypes.registeredFulfillmentTypes = allRegisteredFulfillmentTypes.registeredFulfillmentTypes.concat(registeredFulfillmentTypes); + fulfillment.registeredFulfillmentTypes = fulfillment.registeredFulfillmentTypes.concat(registeredFulfillmentTypes); } - FulfillmentTypeDeclaration.validate(allRegisteredFulfillmentTypes); + FulfillmentTypeDeclaration.validate(fulfillment); } diff --git a/packages/api-plugin-fulfillment/src/resolvers/Query/getFulfillmentTypes.js b/packages/api-plugin-fulfillment/src/resolvers/Query/getFulfillmentTypes.js index f37eb83c2db..588f1c7532a 100644 --- a/packages/api-plugin-fulfillment/src/resolvers/Query/getFulfillmentTypes.js +++ b/packages/api-plugin-fulfillment/src/resolvers/Query/getFulfillmentTypes.js @@ -12,7 +12,7 @@ import { decodeShopOpaqueId } from "../../xforms/id.js"; * @param {String} args.shopId - Shop ID to get records for * @param {Object} context - an object containing the per-request state * @param {Object} info Info about the GraphQL request - * @returns {Promise} Fulfillment methods + * @returns {Promise} Fulfillment types under the Shop */ export default async function getFulfillmentTypes(_, args, context, info) { const { shopId: opaqueShopId, ...connectionArgs } = args; diff --git a/packages/api-plugin-fulfillment/src/resolvers/index.js b/packages/api-plugin-fulfillment/src/resolvers/index.js index 63b65a3443d..3fb14c3d6d5 100644 --- a/packages/api-plugin-fulfillment/src/resolvers/index.js +++ b/packages/api-plugin-fulfillment/src/resolvers/index.js @@ -2,11 +2,6 @@ import FulfillmentMethod from "./FulfillmentMethod/index.js"; import Mutation from "./Mutation/index.js"; import Query from "./Query/index.js"; -/** - * Fulfillment related GraphQL resolvers - * @namespace Fulfillment/GraphQL - */ - export default { FulfillmentMethod, Mutation, diff --git a/packages/api-plugin-fulfillment/src/simpleSchemas.js b/packages/api-plugin-fulfillment/src/simpleSchemas.js index 8ecd68298cd..f204edc4c62 100644 --- a/packages/api-plugin-fulfillment/src/simpleSchemas.js +++ b/packages/api-plugin-fulfillment/src/simpleSchemas.js @@ -16,7 +16,22 @@ export const MethodEmptyData = new SimpleSchema({ } }); - +/** + * @name FulfillmentMethodSchema + * @memberof Schemas + * @type {SimpleSchema} + * @summary Defines Schema for Fulfillment Method + * @property {Number} cost - optional cost + * @property {String[]} fulfillmentTypes - fulfillment type retained for backward compatibility + * @property {String} group - Group to which fulfillment method belong + * @property {Number} handling - handling charges + * @property {Boolean} enabled - status of fulfillment method + * @property {String} label - displayed on the UI + * @property {String} name - name of the fulfillment method, user editable + * @property {String} fulfillmentMethod - used by application, not user editable + * @property {String} displayMessageMethod - used to display additional message on UI specific to ff-method + * @property {Number} rate - ratefor themethod + */ export const FulfillmentMethodSchema = new SimpleSchema({ "cost": { type: Number, @@ -43,13 +58,34 @@ export const FulfillmentMethodSchema = new SimpleSchema({ "rate": Number }); +/** + * @name ProviderSchema + * @memberof Schemas + * @type {SimpleSchema} + * @summary Defines Schema for Provider details inside Fulfillment Type + * @property {Boolean} enabled - status of fulfillment method + * @property {String} label - displayed on the UI + * @property {String} name - name of the fulfillment type, user editable + */ const ProviderSchema = new SimpleSchema({ enabled: Boolean, label: String, name: String }); -export const fulfillmentTypeSchema = new SimpleSchema({ +/** + * @name FulfillmentTypeSchema + * @memberof Schemas + * @type {SimpleSchema} + * @summary Defines Schema for Fulfillment Type + * @property {String} name - name of the fulfillment type, user editable + * @property {String} shopId - Shop ID + * @property {ProviderSchema} provider - Provider details + * @property {String} fulfillmentType - fulfillmentType, not user editable + * @property {String} displayMessageType - used to display additional message on UI specific to ff-type + * @property {FulfillmentMethodSchema[]} methods - array of ff-methods under this ff-type + */ +export const FulfillmentTypeSchema = new SimpleSchema({ "name": String, "shopId": String, "provider": { diff --git a/packages/api-plugin-fulfillment/src/startup.js b/packages/api-plugin-fulfillment/src/startup.js index 509d20ea20d..6229e3bd58f 100644 --- a/packages/api-plugin-fulfillment/src/startup.js +++ b/packages/api-plugin-fulfillment/src/startup.js @@ -5,8 +5,7 @@ * @returns {undefined} */ export default async function fulfillmentTypeUndecidedStartup(context) { - const { collections } = context; - const { Fulfillment } = collections; + const { collections: { Fulfillment } } = context; context.appEvents.on("afterShopCreate", async (payload) => { const { shop } = payload; From d49685783ea94bfeb19693fd26349f511c2546f4 Mon Sep 17 00:00:00 2001 From: Sujith Date: Fri, 28 Oct 2022 00:07:17 +0530 Subject: [PATCH 08/33] fix: removed string concatenation logic Signed-off-by: Sujith --- .../src/mutations/createFulfillmentMethod.js | 3 +-- .../src/queries/getFulfillmentMethods.js | 3 +-- .../getFulfillmentMethodsWithQuotes.js | 21 +++---------------- .../src/queries/getFulfillmentTypes.js | 3 +-- 4 files changed, 6 insertions(+), 24 deletions(-) diff --git a/packages/api-plugin-fulfillment/src/mutations/createFulfillmentMethod.js b/packages/api-plugin-fulfillment/src/mutations/createFulfillmentMethod.js index abd769e76b2..0d1226c9ad9 100644 --- a/packages/api-plugin-fulfillment/src/mutations/createFulfillmentMethod.js +++ b/packages/api-plugin-fulfillment/src/mutations/createFulfillmentMethod.js @@ -30,8 +30,7 @@ export default async function createFulfillmentMethodMutation(context, input) { inputSchema.validate(cleanedInput); const { method: inputMethod, fulfillmentTypeId, shopId } = cleanedInput; - const { collections } = context; - const { Fulfillment } = collections; + const { collections: { Fulfillment } } = context; const method = { ...inputMethod }; await context.validatePermissions("reaction:legacy:fulfillmentMethods", "create", { shopId }); diff --git a/packages/api-plugin-fulfillment/src/queries/getFulfillmentMethods.js b/packages/api-plugin-fulfillment/src/queries/getFulfillmentMethods.js index 3731ea54832..eedfa584ccb 100644 --- a/packages/api-plugin-fulfillment/src/queries/getFulfillmentMethods.js +++ b/packages/api-plugin-fulfillment/src/queries/getFulfillmentMethods.js @@ -10,8 +10,7 @@ * @returns {Promise} Mongo cursor */ export default async function getFulfillmentMethods(context, input) { - const { collections } = context; - const { Fulfillment } = collections; + const { collections: { Fulfillment } } = context; const { shopId, fulfillmentTypeId } = input; await context.validatePermissions("reaction:legacy:fulfillmentTypes", "read", { shopId }); diff --git a/packages/api-plugin-fulfillment/src/queries/getFulfillmentMethodsWithQuotes.js b/packages/api-plugin-fulfillment/src/queries/getFulfillmentMethodsWithQuotes.js index a380cbf744c..7c3957ef519 100644 --- a/packages/api-plugin-fulfillment/src/queries/getFulfillmentMethodsWithQuotes.js +++ b/packages/api-plugin-fulfillment/src/queries/getFulfillmentMethodsWithQuotes.js @@ -4,21 +4,6 @@ import extendCommonOrder from "../util/extendCommonOrder.js"; const logCtx = { name: "fulfillment", file: "getFulfillmentMethodsWithQuotes" }; -/** - * @name getTitleCaseOfWord - * @method - * @summary converts the given word to Title case - * @param {String} inputWord - input word - * @returns {String} return Title case of word - * @private - */ -function getTitleCaseOfWord(inputWord) { - let outWord = String(inputWord); - outWord = outWord.toLowerCase(); - outWord = outWord.charAt(0).toUpperCase() + outWord.slice(1); - return outWord; -} - /** * @name getFulfillmentMethodsWithQuotes * @method @@ -42,9 +27,9 @@ export default async function getFulfillmentMethodsWithQuotes(commonOrder, conte const fulfillmentTypeInGroup = commonOrder.fulfillmentType; if (!fulfillmentTypeInGroup) throw new ReactionError("not-found", "Fulfillment type not found in commonOrder"); - const fulfillmentTypeTitleCase = getTitleCaseOfWord(fulfillmentTypeInGroup); - const functionTypesToCall = `getFulfillmentMethodsWithQuotes${fulfillmentTypeTitleCase}`; - const funcs = context.getFunctionsOfType(functionTypesToCall); + const allFuncsArray = context.getFunctionsOfType("getFulfillmentMethodsWithQuotes"); + const ffTypeFuncObjects = allFuncsArray.filter((func) => fulfillmentTypeInGroup === func.key); + const funcs = ffTypeFuncObjects.map((func) => func.handler); if (!funcs || !Array.isArray(funcs) || !funcs.length) throw new ReactionError("not-found", `No methods for Fulfillment type ${fulfillmentTypeInGroup}`); diff --git a/packages/api-plugin-fulfillment/src/queries/getFulfillmentTypes.js b/packages/api-plugin-fulfillment/src/queries/getFulfillmentTypes.js index 546d01d672f..ba2e20e3ca9 100644 --- a/packages/api-plugin-fulfillment/src/queries/getFulfillmentTypes.js +++ b/packages/api-plugin-fulfillment/src/queries/getFulfillmentTypes.js @@ -9,8 +9,7 @@ * @returns {Promise} Mongo cursor */ export default async function getFulfillmentTypes(context, input) { - const { collections } = context; - const { Fulfillment } = collections; + const { collections: { Fulfillment } } = context; const { shopId } = input; await context.validatePermissions("reaction:legacy:fulfillmentTypes", "read", { shopId }); From 349e1cd2fcd5ca1f6e3f46f7167b8f5441ac034e Mon Sep 17 00:00:00 2001 From: Sujith Date: Fri, 28 Oct 2022 17:07:28 +0530 Subject: [PATCH 09/33] fix: use queries.getCartById delete /util version Signed-off-by: Sujith --- .../selectFulfillmentOptionForGroup.js | 3 +- .../selectFulfillmentOptionForGroup.test.js | 4 +-- .../updateFulfillmentOptionsForGroup.js | 3 +- .../updateFulfillmentOptionsForGroup.test.js | 4 +-- .../src/util/getCartById.js | 34 ------------------- 5 files changed, 6 insertions(+), 42 deletions(-) delete mode 100644 packages/api-plugin-fulfillment/src/util/getCartById.js diff --git a/packages/api-plugin-fulfillment/src/mutations/selectFulfillmentOptionForGroup.js b/packages/api-plugin-fulfillment/src/mutations/selectFulfillmentOptionForGroup.js index f6a56024e21..7eb6c054985 100644 --- a/packages/api-plugin-fulfillment/src/mutations/selectFulfillmentOptionForGroup.js +++ b/packages/api-plugin-fulfillment/src/mutations/selectFulfillmentOptionForGroup.js @@ -1,6 +1,5 @@ import SimpleSchema from "simpl-schema"; import ReactionError from "@reactioncommerce/reaction-error"; -import getCartById from "../util/getCartById.js"; const inputSchema = new SimpleSchema({ cartId: String, @@ -30,7 +29,7 @@ export default async function selectFulfillmentOptionForGroup(context, input) { const { cartId, cartToken, fulfillmentGroupId, fulfillmentMethodId } = cleanedInput; - const cart = await getCartById(context, cartId, { cartToken, throwIfNotFound: true }); + const cart = await context.queries.getCartById(context, cartId, { cartToken, throwIfNotFound: true }); const fulfillmentGroup = (cart.shipping || []).find((group) => group._id === fulfillmentGroupId); if (!fulfillmentGroup) throw new ReactionError("not-found", `Fulfillment group with ID ${fulfillmentGroupId} not found in cart with ID ${cartId}`); diff --git a/packages/api-plugin-fulfillment/src/mutations/selectFulfillmentOptionForGroup.test.js b/packages/api-plugin-fulfillment/src/mutations/selectFulfillmentOptionForGroup.test.js index 9068349c76d..9b6e362af3a 100644 --- a/packages/api-plugin-fulfillment/src/mutations/selectFulfillmentOptionForGroup.test.js +++ b/packages/api-plugin-fulfillment/src/mutations/selectFulfillmentOptionForGroup.test.js @@ -1,7 +1,7 @@ import mockContext from "@reactioncommerce/api-utils/tests/mockContext.js"; import selectFulfillmentOptionForGroup from "./selectFulfillmentOptionForGroup.js"; -jest.mock("../util/getCartById", () => jest.fn().mockImplementation(() => Promise.resolve({ +mockContext.queries.getCartById = jest.fn().mockName("getCartById").mockReturnValueOnce(Promise.resolve({ _id: "cartId", shipping: [{ _id: "group1", @@ -14,7 +14,7 @@ jest.mock("../util/getCartById", () => jest.fn().mockImplementation(() => Promis }], type: "shipping" }] -}))); +})); beforeAll(() => { if (!mockContext.mutations.saveCart) { diff --git a/packages/api-plugin-fulfillment/src/mutations/updateFulfillmentOptionsForGroup.js b/packages/api-plugin-fulfillment/src/mutations/updateFulfillmentOptionsForGroup.js index d008c1a3e5c..78de9b922e7 100644 --- a/packages/api-plugin-fulfillment/src/mutations/updateFulfillmentOptionsForGroup.js +++ b/packages/api-plugin-fulfillment/src/mutations/updateFulfillmentOptionsForGroup.js @@ -1,7 +1,6 @@ import _ from "lodash"; import SimpleSchema from "simpl-schema"; import ReactionError from "@reactioncommerce/reaction-error"; -import getCartById from "../util/getCartById.js"; const inputSchema = new SimpleSchema({ cartId: String, @@ -66,7 +65,7 @@ export default async function updateFulfillmentOptionsForGroup(context, input) { const { cartId, cartToken, fulfillmentGroupId } = cleanedInput; - const cart = await getCartById(context, cartId, { cartToken, throwIfNotFound: true }); + const cart = await context.queries.getCartById(context, cartId, { cartToken, throwIfNotFound: true }); // This is done by `saveCart`, too, but we need to do it before every call to `getCommonOrderForCartGroup` // to avoid errors in the case where a product has been deleted since the last time this cart was saved. diff --git a/packages/api-plugin-fulfillment/src/mutations/updateFulfillmentOptionsForGroup.test.js b/packages/api-plugin-fulfillment/src/mutations/updateFulfillmentOptionsForGroup.test.js index 2dbbc8a9b0f..ce97fd76b18 100644 --- a/packages/api-plugin-fulfillment/src/mutations/updateFulfillmentOptionsForGroup.test.js +++ b/packages/api-plugin-fulfillment/src/mutations/updateFulfillmentOptionsForGroup.test.js @@ -2,7 +2,7 @@ import mockContext from "@reactioncommerce/api-utils/tests/mockContext.js"; import Factory from "../tests/factory.js"; import updateFulfillmentOptionsForGroup from "./updateFulfillmentOptionsForGroup.js"; -jest.mock("../util/getCartById", () => jest.fn().mockImplementation(() => Promise.resolve({ +mockContext.queries.getCartById = jest.fn().mockName("getCartById").mockReturnValueOnce(Promise.resolve({ _id: "cartId", items: [{ _id: "123", @@ -21,7 +21,7 @@ jest.mock("../util/getCartById", () => jest.fn().mockImplementation(() => Promis itemIds: ["123"], type: "shipping" }] -}))); +})); const fakeCart = Factory.Cart.makeOne(); const fakeQuote = Factory.ShipmentQuote.makeOne(); diff --git a/packages/api-plugin-fulfillment/src/util/getCartById.js b/packages/api-plugin-fulfillment/src/util/getCartById.js deleted file mode 100644 index 3142f9d4f69..00000000000 --- a/packages/api-plugin-fulfillment/src/util/getCartById.js +++ /dev/null @@ -1,34 +0,0 @@ -import hashToken from "@reactioncommerce/api-utils/hashToken.js"; -import ReactionError from "@reactioncommerce/reaction-error"; - -/** - * @summary Gets a cart from the db by ID. If there is an account for the request, verifies that the - * account has permission to access the cart. Optionally throws an error if not found. - * @param {Object} context Object defining the request state - * @param {String} cartId The cart ID - * @param {Object} [options] Options - * @param {String} [options.cartToken] Cart token, required if it's an anonymous cart - * @param {Boolean} [options.throwIfNotFound] Default false. Throw a not-found error rather than return null `cart` - * @returns {Object|null} The cart document, or null if not found and `throwIfNotFound` was false - */ -export default async function getCartById(context, cartId, { cartToken, throwIfNotFound = false } = {}) { - const { accountId, collections } = context; - const { Cart } = collections; - - const selector = { _id: cartId }; - - if (cartToken) { - selector.anonymousAccessToken = hashToken(cartToken); - } - - const cart = await Cart.findOne(selector); - if (!cart && throwIfNotFound) { - throw new ReactionError("not-found", "Cart not found"); - } - - if (cart && cart.accountId && cart.accountId !== accountId) { - throw new ReactionError("access-denied", "Access Denied"); - } - - return cart || null; -} From f7a8aaeab866b7f58f268232599197d480fdca2e Mon Sep 17 00:00:00 2001 From: Sushmitha Malae Date: Wed, 19 Oct 2022 17:12:28 +0530 Subject: [PATCH 10/33] fix: updateSurcharge returns the entire document from db Signed-off-by: m_sushmitha --- .../updateSurcharge-return-full-item.md | 5 ++ .../src/mutations/updateSurcharge.js | 13 +++-- .../src/mutations/updateSurcharge.test.js | 54 ++++++------------- 3 files changed, 26 insertions(+), 46 deletions(-) create mode 100644 .changeset/updateSurcharge-return-full-item.md diff --git a/.changeset/updateSurcharge-return-full-item.md b/.changeset/updateSurcharge-return-full-item.md new file mode 100644 index 00000000000..c6e247b4a42 --- /dev/null +++ b/.changeset/updateSurcharge-return-full-item.md @@ -0,0 +1,5 @@ +--- +"@reactioncommerce/api-plugin-surcharges": patch +--- + +Fixed Update surcharge mutation to return the whole item from db diff --git a/packages/api-plugin-surcharges/src/mutations/updateSurcharge.js b/packages/api-plugin-surcharges/src/mutations/updateSurcharge.js index 8b3e863156a..7446fa1bf04 100644 --- a/packages/api-plugin-surcharges/src/mutations/updateSurcharge.js +++ b/packages/api-plugin-surcharges/src/mutations/updateSurcharge.js @@ -37,13 +37,12 @@ export default async function updateSurchargeMutation(context, input) { Surcharge.validate(modifier, { modifier: true }); - const { matchedCount } = await Surcharges.updateOne({ - _id: surchargeId, - shopId - }, modifier); - surcharge._id = surchargeId; - surcharge.shopId = shopId; + const { matchedCount, value: updatedSurcharge } = await Surcharges.findOneAndUpdate( + { _id: surchargeId, shopId }, + modifier, + { returnOriginal: false } + ); if (matchedCount === 0) throw new ReactionError("not-found", "Not found"); - return { surcharge }; + return { surcharge: updatedSurcharge }; } diff --git a/packages/api-plugin-surcharges/src/mutations/updateSurcharge.test.js b/packages/api-plugin-surcharges/src/mutations/updateSurcharge.test.js index e86f74d793e..02502ae5754 100644 --- a/packages/api-plugin-surcharges/src/mutations/updateSurcharge.test.js +++ b/packages/api-plugin-surcharges/src/mutations/updateSurcharge.test.js @@ -9,27 +9,28 @@ mockContext.validatePermissions.mockReturnValueOnce(Promise.resolve(null)); const createdAt = new Date(); -const surcharge = { +const updatedSurcharge = { + updatedAt: jasmine.any(Date), type: "surcharge", attributes: [ - { property: "vendor", value: "reaction", propertyType: "string", operator: "eq" }, - { property: "productType", value: "knife", propertyType: "string", operator: "eq" } + { property: "vendor", value: "john", propertyType: "string", operator: "eq" }, + { property: "productType", value: "gun", propertyType: "string", operator: "eq" } ], createdAt, - destination: { region: ["CO", "NY"] }, - amount: 5.99, + destination: { region: ["NJ", "WY"] }, + amount: 17.99, messagesByLanguage: [ { - content: "Original Message English", + content: "Updated Message English", language: "en" }, { - content: "Original Message Spanish", + content: "Updated Message Spanish", language: "es" } ] }; -const updatedSurcharge = { +const newSurcharge = { type: "surcharge", attributes: [ { property: "vendor", value: "john", propertyType: "string", operator: "eq" }, @@ -37,10 +38,7 @@ const updatedSurcharge = { ], createdAt, destination: { region: ["NJ", "WY"] }, - amount: { - amount: 17.99, - currencyCode: "USD" - }, + amount: 17.99, messagesByLanguage: [ { content: "Updated Message English", @@ -53,38 +51,16 @@ const updatedSurcharge = { }; test("update a surcharge", async () => { - mockContext.collections.Surcharges.updateOne.mockReturnValueOnce(Promise.resolve({ - ok: 1, - updatedSurcharge + mockContext.collections.Surcharges.findOneAndUpdate.mockReturnValueOnce(Promise.resolve({ + matchedCount: 1, + value: updatedSurcharge })); const result = await updateSurchargeMutation(mockContext, { - surcharge, surchargeId: "surcharge123", + surcharge: newSurcharge, shopId: "shop123" }); - expect(result).toEqual({ - surcharge: { - _id: "surcharge123", - shopId: "shop123", - type: "surcharge", - attributes: [ - { property: "vendor", value: "reaction", propertyType: "string", operator: "eq" }, - { property: "productType", value: "knife", propertyType: "string", operator: "eq" } - ], - createdAt, - destination: { region: ["CO", "NY"] }, - amount: 5.99, - messagesByLanguage: [ - { - content: "Original Message English", - language: "en" - }, { - content: "Original Message Spanish", - language: "es" - } - ] - } - }); + expect(result.surcharge).toEqual(updatedSurcharge); }); From d4853d0f30ddb9ec9bb9987c1c5a65e9f489bdcf Mon Sep 17 00:00:00 2001 From: apadhi Date: Mon, 17 Oct 2022 17:57:31 +0530 Subject: [PATCH 11/33] moved jest process env to a separate file Signed-off-by: apadhi --- apps/reaction/tests/util/jestProcessEnv.json | 6 ++++++ apps/reaction/tests/util/setupJestTests.js | 10 ++++------ 2 files changed, 10 insertions(+), 6 deletions(-) create mode 100644 apps/reaction/tests/util/jestProcessEnv.json diff --git a/apps/reaction/tests/util/jestProcessEnv.json b/apps/reaction/tests/util/jestProcessEnv.json new file mode 100644 index 00000000000..e08857ecd30 --- /dev/null +++ b/apps/reaction/tests/util/jestProcessEnv.json @@ -0,0 +1,6 @@ +{ + "MAIL_URL": "smtp://user:pass@email-smtp.us-west-2.amazonaws.com:465", + "REACTION_LOG_LEVEL": "ERROR", + "REACTION_WORKERS_ENABLED": false +} + diff --git a/apps/reaction/tests/util/setupJestTests.js b/apps/reaction/tests/util/setupJestTests.js index e3cf87be8da..1fff30555cf 100644 --- a/apps/reaction/tests/util/setupJestTests.js +++ b/apps/reaction/tests/util/setupJestTests.js @@ -1,11 +1,9 @@ /* eslint-disable no-undef */ - -require("../../src/checkNodeVersion.cjs"); - +import { createRequire } from "module"; +const require = createRequire(import.meta.url); +const jestProcessEnv = require("./jestProcessEnv.json"); process.env = Object.assign(process.env, { - MAIL_URL: "smtp://user:pass@email-smtp.us-west-2.amazonaws.com:465", - REACTION_LOG_LEVEL: "ERROR", - REACTION_WORKERS_ENABLED: false + ...jestProcessEnv }); process.on("unhandledRejection", (err) => { From 1451b405c7b0d450517408699c2f2922fea05605 Mon Sep 17 00:00:00 2001 From: apadhi Date: Tue, 18 Oct 2022 09:20:39 +0530 Subject: [PATCH 12/33] fix: updated gitignore Signed-off-by: apadhi --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index ebd1179ac14..b2f77245881 100644 --- a/.gitignore +++ b/.gitignore @@ -79,3 +79,4 @@ yalc-packages # Build dist +.idea From c1f1234854ba2436a040a734acea5de228da1bb9 Mon Sep 17 00:00:00 2001 From: apadhi Date: Tue, 18 Oct 2022 09:38:38 +0530 Subject: [PATCH 13/33] fix: fixed lint error Signed-off-by: apadhi --- apps/reaction/tests/util/setupJestTests.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/apps/reaction/tests/util/setupJestTests.js b/apps/reaction/tests/util/setupJestTests.js index 1fff30555cf..6b9aeabb62e 100644 --- a/apps/reaction/tests/util/setupJestTests.js +++ b/apps/reaction/tests/util/setupJestTests.js @@ -1,7 +1,9 @@ /* eslint-disable no-undef */ import { createRequire } from "module"; + const require = createRequire(import.meta.url); const jestProcessEnv = require("./jestProcessEnv.json"); + process.env = Object.assign(process.env, { ...jestProcessEnv }); From a26cb3c7ecbb67f228041ba77684cdf217e63138 Mon Sep 17 00:00:00 2001 From: apadhi Date: Tue, 18 Oct 2022 09:51:08 +0530 Subject: [PATCH 14/33] fix: empty commit to re-trigger build Signed-off-by: apadhi From 12d44ebf3a3899bc74ff640a341ebeaa1def1701 Mon Sep 17 00:00:00 2001 From: Pradeep Kumar Duvvur Date: Tue, 18 Oct 2022 14:00:06 -0700 Subject: [PATCH 15/33] docs: Fix typo for node version manager Fixing typo of `nvm` in Readme Signed-off-by: Pradeep Kumar Duvvur --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 9081607547b..b4fa735a670 100644 --- a/README.md +++ b/README.md @@ -39,7 +39,7 @@ To start working with your own project built on Mailchimp Open Commerce you can the quickest and easiest way to develop on Open Commerce. It allows you to create and work with API, Admin, and Storefront projects all via the command line. ## What you need -- We recommend installing [nmv](https://github.com/nvm-sh/nvm) +- We recommend installing [nvm](https://github.com/nvm-sh/nvm) - [14.18.1 ≤ Node version < 16](https://nodejs.org/ja/blog/release/v14.18.1/) - [Git](https://git-scm.com/) - [Docker](https://www.docker.com/get-started/) From b14a752653d3c90e5bd2afbc6a77bcb2a6e5d2d9 Mon Sep 17 00:00:00 2001 From: Jayaraman N R Date: Wed, 19 Oct 2022 22:52:31 +0530 Subject: [PATCH 16/33] fix: allow restrictions on nested properties Signed-off-by: Jayaraman N R --- packages/api-plugin-shipments-flat-rate/package.json | 3 ++- .../src/util/attributeDenyCheck.js | 8 +++++--- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/packages/api-plugin-shipments-flat-rate/package.json b/packages/api-plugin-shipments-flat-rate/package.json index 6189f86311e..7b284aae128 100644 --- a/packages/api-plugin-shipments-flat-rate/package.json +++ b/packages/api-plugin-shipments-flat-rate/package.json @@ -30,7 +30,8 @@ "@reactioncommerce/logger": "^1.1.3", "@reactioncommerce/random": "^1.0.2", "@reactioncommerce/reaction-error": "^1.0.1", - "simpl-schema": "^1.12.0" + "simpl-schema": "^1.12.0", + "lodash": "^4.17.15" }, "devDependencies": { "@babel/core": "^7.7.7", diff --git a/packages/api-plugin-shipments-flat-rate/src/util/attributeDenyCheck.js b/packages/api-plugin-shipments-flat-rate/src/util/attributeDenyCheck.js index 93be1ca8aea..f0294b698cf 100644 --- a/packages/api-plugin-shipments-flat-rate/src/util/attributeDenyCheck.js +++ b/packages/api-plugin-shipments-flat-rate/src/util/attributeDenyCheck.js @@ -1,3 +1,4 @@ +import _ from "lodash"; import operators from "@reactioncommerce/api-utils/operators.js"; import propertyTypes from "@reactioncommerce/api-utils/propertyTypes.js"; import isDestinationRestricted from "./isDestinationRestricted.js"; @@ -25,12 +26,13 @@ export async function attributeDenyCheck(methodRestrictions, method, hydratedOrd // Item must meet all attributes to be restricted return attributes.every((attribute) => { - let attributeFound = operators[attribute.operator](item[attribute.property], propertyTypes[attribute.propertyType](attribute.value)); + const attributePropertyValue = _.get(item, attribute.property); + let attributeFound = operators[attribute.operator](attributePropertyValue, propertyTypes[attribute.propertyType](attribute.value)); // If attribute is an array on the item, use `includes` instead of checking for === // This works for tags, tagIds, and any future attribute that might be an array - if (Array.isArray(item[attribute.property])) { - attributeFound = item[attribute.property].includes(attribute.value); + if (Array.isArray(attributePropertyValue)) { + attributeFound = attributePropertyValue.includes(attribute.value); } if (attributeFound) { From 61002e1c6847e52593d96cbd8eab7dd560242cb7 Mon Sep 17 00:00:00 2001 From: Jayaraman N R Date: Thu, 20 Oct 2022 10:39:54 +0530 Subject: [PATCH 17/33] fix: add lock file, tests and changeset Signed-off-by: Jayaraman N R --- .changeset/modern-poems-smash.md | 5 + .../src/util/filterShippingMethods.test.js | 91 ++++++++++++++++++- pnpm-lock.yaml | 8 +- 3 files changed, 100 insertions(+), 4 deletions(-) create mode 100644 .changeset/modern-poems-smash.md diff --git a/.changeset/modern-poems-smash.md b/.changeset/modern-poems-smash.md new file mode 100644 index 00000000000..4a417e17643 --- /dev/null +++ b/.changeset/modern-poems-smash.md @@ -0,0 +1,5 @@ +--- +"@reactioncommerce/api-plugin-shipments-flat-rate": patch +--- + +allow nested properties to be accessible for shipment restrictions. diff --git a/packages/api-plugin-shipments-flat-rate/src/util/filterShippingMethods.test.js b/packages/api-plugin-shipments-flat-rate/src/util/filterShippingMethods.test.js index 20cbe72d1e2..69f0d1feab6 100644 --- a/packages/api-plugin-shipments-flat-rate/src/util/filterShippingMethods.test.js +++ b/packages/api-plugin-shipments-flat-rate/src/util/filterShippingMethods.test.js @@ -52,7 +52,17 @@ const mockHydratedOrderItems = { variantId: "tMkp5QwZog5ihYTfG", weight: 50, width: 10, - tags: [Array] + tags: [Array], + subtotal: { + amount: 12.99, + currencyCode: "USD" + }, + parcel: { + height: 10, + weight: 50, + width: 10, + length: 10 + } }; const mockHydratedOrder = { @@ -814,3 +824,82 @@ test("deny method - multiple attributes - item value is less than $100 AND item expect(allowedMethods).toEqual([]); }); + +/* +* Tests with nested item/attribute restrictions +*/ +test("deny method - nested attribute - subtotal.amount is less than $100, item restricted", async () => { + const mockMethodRestrictions = [ + { + _id: "allow001", + methodIds: [ + "stviZaLdqRvTKW6J5" + ], + type: "allow", + destination: { + country: [ + "US" + ] + } + }, + { + _id: "deny001", + methodIds: [ + "stviZaLdqRvTKW6J5" + ], + type: "deny", + attributes: [ + { + property: "subtotal.amount", + value: 100, + propertyType: "int", + operator: "lt" + } + ] + } + ]; + + mockContext.collections.FlatRateFulfillmentRestrictions.toArray.mockReturnValue(Promise.resolve(mockMethodRestrictions)); + + const allowedMethods = await filterShippingMethods(mockContext, mockShippingMethod, mockHydratedOrder); + + expect(allowedMethods).toEqual([]); +}); + +test("deny method - nested attributes - parcel.weight is greater than 50, no item restrictions", async () => { + const mockMethodRestrictions = [ + { + _id: "allow001", + methodIds: [ + "stviZaLdqRvTKW6J5" + ], + type: "allow", + destination: { + country: [ + "US" + ] + } + }, + { + _id: "deny001", + methodIds: [ + "stviZaLdqRvTKW6J5" + ], + type: "deny", + attributes: [ + { + property: "parcel.weight", + value: 50, + propertyType: "int", + operator: "gt" + } + ] + } + ]; + + mockContext.collections.FlatRateFulfillmentRestrictions.toArray.mockReturnValue(Promise.resolve(mockMethodRestrictions)); + + const allowedMethods = await filterShippingMethods(mockContext, mockShippingMethod, mockHydratedOrder); + + expect(allowedMethods).toEqual(mockShippingMethod); +}); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 1ab7367d529..982006ee9f3 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -245,7 +245,7 @@ importers: '@reactioncommerce/logger': link:../../packages/logger '@reactioncommerce/nodemailer': 5.0.5 '@reactioncommerce/random': link:../../packages/random - '@snyk/protect': 1.1032.0 + '@snyk/protect': 1.1035.0 graphql: 14.7.0 semver: 6.3.0 sharp: 0.29.3 @@ -1148,12 +1148,14 @@ importers: babel-plugin-rewire-exports: ^2.0.0 babel-plugin-transform-es2015-modules-commonjs: ^6.26.2 babel-plugin-transform-import-meta: ~1.0.0 + lodash: ^4.17.15 simpl-schema: ^1.12.0 dependencies: '@reactioncommerce/api-utils': link:../api-utils '@reactioncommerce/logger': link:../logger '@reactioncommerce/random': link:../random '@reactioncommerce/reaction-error': link:../reaction-error + lodash: 4.17.21 simpl-schema: 1.12.3 devDependencies: '@babel/core': 7.19.0 @@ -4727,8 +4729,8 @@ packages: '@sinonjs/commons': 1.8.3 dev: false - /@snyk/protect/1.1032.0: - resolution: {integrity: sha512-C6EvuhDycvmyMCmhjC/KAV1z3aykVGKrYhGRI5u5Buzz27HgjHhB4DSC3MDpyljpahtAkCS/E7pAwo9zP3m4bA==} + /@snyk/protect/1.1035.0: + resolution: {integrity: sha512-8s1HygVBFDmNn7HdDTslb8LyLBKdtexexl8lsbR4W1o0/JgXDxOSEztTRakVdONFOnlRNbS3DtEBvVRdHiYFVg==} engines: {node: '>=10'} hasBin: true dev: false From 82be5160f8414d0308bc118b2afbb2f488c8e433 Mon Sep 17 00:00:00 2001 From: Brent Hoover Date: Thu, 20 Oct 2022 12:31:14 +0000 Subject: [PATCH 18/33] chore: remove yalc example file Signed-off-by: Brent Hoover --- apps/reaction/yalc-packages.example | 38 ----------------------------- 1 file changed, 38 deletions(-) delete mode 100644 apps/reaction/yalc-packages.example diff --git a/apps/reaction/yalc-packages.example b/apps/reaction/yalc-packages.example deleted file mode 100644 index 11f1d6577f7..00000000000 --- a/apps/reaction/yalc-packages.example +++ /dev/null @@ -1,38 +0,0 @@ -../api-plugins/api-core=true -../api-plugins/api-plugin-accounts=true -../api-plugins/api-plugin-address-validation=true -../api-plugins/api-plugin-address-validation-test=true -../api-plugins/api-plugin-authentication=true -../api-plugins/api-plugin-authorization-simple=true -../api-plugins/api-plugin-carts=true -../api-plugins/api-plugin-catalogs=true -../api-plugins/api-plugin-discounts=true -../api-plugins/api-plugin-discounts-codes=true -../api-plugins/api-plugin-email=true -../api-plugins/api-plugin-email-smtp=true -../api-plugins/api-plugin-email-templates=true -../api-plugins/api-plugin-files=true -../api-plugins/api-plugin-i18n=true -../api-plugins/api-plugin-inventory=true -../api-plugins/api-plugin-inventory-simple=true -../api-plugins/api-plugin-job-queue=true -../api-plugins/api-plugin-navigation=true -../api-plugins/api-plugin-notifications=true -../api-plugins/api-plugin-orders=true -../api-plugins/api-plugin-payments=true -../api-plugins/api-plugin-payments-example=true -../api-plugins/api-plugin-payments-stripe=true -../api-plugins/api-plugin-pricing-simple=true -../api-plugins/api-plugin-products=true -../api-plugins/api-plugin-settings=true -../api-plugins/api-plugin-shipments=true -../api-plugins/api-plugin-shipments-flat-rate=true -../api-plugins/api-plugin-shops=true -../api-plugins/api-plugin-simple-schema=true -../api-plugins/api-plugin-sitemap-generator=true -../api-plugins/api-plugin-surcharges=true -../api-plugins/api-plugin-system-information=true -../api-plugins/api-plugin-tags=true -../api-plugins/api-plugin-taxes=true -../api-plugins/api-plugin-taxes-flat-rate=true -../api-plugins/api-plugin-translations=true From 3cbe960b97ebc12f95d65bd347cbc9ed19deb59b Mon Sep 17 00:00:00 2001 From: vanpho93 Date: Fri, 21 Oct 2022 14:15:11 +0700 Subject: [PATCH 19/33] feat: change reaction dependencies version --- apps/reaction/package.json | 90 +++++++++++++++++++------------------- 1 file changed, 45 insertions(+), 45 deletions(-) diff --git a/apps/reaction/package.json b/apps/reaction/package.json index ba12420c0ec..5fe143e5b8b 100644 --- a/apps/reaction/package.json +++ b/apps/reaction/package.json @@ -20,51 +20,51 @@ "url": "https://github.com/reactioncommerce/reaction/issues" }, "dependencies": { - "@reactioncommerce/api-core": "^2.0.0", - "@reactioncommerce/api-plugin-accounts": "^2.0.0", - "@reactioncommerce/api-plugin-address-validation": "^1.3.1", - "@reactioncommerce/api-plugin-address-validation-test": "^1.0.3", - "@reactioncommerce/api-plugin-authentication": "^2.2.3", - "@reactioncommerce/api-plugin-authorization-simple": "^1.3.1", - "@reactioncommerce/api-plugin-carts": "^1.2.4", - "@reactioncommerce/api-plugin-catalogs": "^1.1.2", - "@reactioncommerce/api-plugin-discounts": "^1.0.1", - "@reactioncommerce/api-plugin-discounts-codes": "^1.2.0", - "@reactioncommerce/api-plugin-email": "^1.1.0", - "@reactioncommerce/api-plugin-email-smtp": "^1.0.5", - "@reactioncommerce/api-plugin-email-templates": "^1.1.1", - "@reactioncommerce/api-plugin-files": "^1.0.19", - "@reactioncommerce/api-plugin-i18n": "^1.0.4", - "@reactioncommerce/api-plugin-inventory": "^1.0.3", - "@reactioncommerce/api-plugin-inventory-simple": "^1.0.4", - "@reactioncommerce/api-plugin-job-queue": "^1.0.2", - "@reactioncommerce/api-plugin-navigation": "^1.1.1", - "@reactioncommerce/api-plugin-notifications": "^1.1.0", - "@reactioncommerce/api-plugin-orders": "^1.4.2", - "@reactioncommerce/api-plugin-payments": "^1.0.1", - "@reactioncommerce/api-plugin-payments-example": "^1.1.1", - "@reactioncommerce/api-plugin-payments-stripe-sca": "^1.0.2", - "@reactioncommerce/api-plugin-pricing-simple": "^1.0.3", - "@reactioncommerce/api-plugin-products": "^1.0.2", - "@reactioncommerce/api-plugin-settings": "^1.0.2", - "@reactioncommerce/api-plugin-shipments": "^1.0.1", - "@reactioncommerce/api-plugin-shipments-flat-rate": "^1.0.4", - "@reactioncommerce/api-plugin-shops": "^1.2.1", - "@reactioncommerce/api-plugin-simple-schema": "^1.0.3", - "@reactioncommerce/api-plugin-sitemap-generator": "^1.2.2", - "@reactioncommerce/api-plugin-surcharges": "^1.1.7", - "@reactioncommerce/api-plugin-system-information": "^1.1.3", - "@reactioncommerce/api-plugin-tags": "^1.1.1", - "@reactioncommerce/api-plugin-taxes": "^1.1.1", - "@reactioncommerce/api-plugin-taxes-flat-rate": "^1.0.4", - "@reactioncommerce/api-plugin-translations": "^1.2.0", - "@reactioncommerce/api-utils": "^1.16.5", - "@reactioncommerce/db-version-check": "^1.0.0", - "@reactioncommerce/file-collections": "^0.9.3", - "@reactioncommerce/file-collections-sa-gridfs": "^0.1.5", - "@reactioncommerce/logger": "^1.1.3", - "@reactioncommerce/nodemailer": "^5.0.5", - "@reactioncommerce/random": "^1.0.2", + "@reactioncommerce/api-core": "2.0.1", + "@reactioncommerce/api-plugin-accounts": "2.0.4", + "@reactioncommerce/api-plugin-address-validation": "1.3.3", + "@reactioncommerce/api-plugin-address-validation-test": "1.0.3", + "@reactioncommerce/api-plugin-authentication": "2.2.5", + "@reactioncommerce/api-plugin-authorization-simple": "1.3.2", + "@reactioncommerce/api-plugin-carts": "1.3.5", + "@reactioncommerce/api-plugin-catalogs": "1.1.2", + "@reactioncommerce/api-plugin-discounts": "1.0.4", + "@reactioncommerce/api-plugin-discounts-codes": "1.2.4", + "@reactioncommerce/api-plugin-email": "1.1.5", + "@reactioncommerce/api-plugin-email-smtp": "1.0.8", + "@reactioncommerce/api-plugin-email-templates": "1.1.7", + "@reactioncommerce/api-plugin-files": "1.0.19", + "@reactioncommerce/api-plugin-i18n": "1.0.6", + "@reactioncommerce/api-plugin-inventory": "1.0.5", + "@reactioncommerce/api-plugin-inventory-simple": "1.0.6", + "@reactioncommerce/api-plugin-job-queue": "1.0.7", + "@reactioncommerce/api-plugin-navigation": "1.1.2", + "@reactioncommerce/api-plugin-notifications": "1.1.4", + "@reactioncommerce/api-plugin-orders": "1.4.5", + "@reactioncommerce/api-plugin-payments": "1.0.5", + "@reactioncommerce/api-plugin-payments-example": "1.1.2", + "@reactioncommerce/api-plugin-payments-stripe-sca": "1.0.2", + "@reactioncommerce/api-plugin-pricing-simple": "1.0.7", + "@reactioncommerce/api-plugin-products": "1.3.0", + "@reactioncommerce/api-plugin-settings": "1.0.7", + "@reactioncommerce/api-plugin-shipments": "1.0.3", + "@reactioncommerce/api-plugin-shipments-flat-rate": "1.0.9", + "@reactioncommerce/api-plugin-shops": "1.3.0", + "@reactioncommerce/api-plugin-simple-schema": "1.0.3", + "@reactioncommerce/api-plugin-sitemap-generator": "1.2.3", + "@reactioncommerce/api-plugin-surcharges": "1.1.7", + "@reactioncommerce/api-plugin-system-information": "1.1.4", + "@reactioncommerce/api-plugin-tags": "1.2.0", + "@reactioncommerce/api-plugin-taxes": "1.1.3", + "@reactioncommerce/api-plugin-taxes-flat-rate": "1.0.8", + "@reactioncommerce/api-plugin-translations": "1.2.2", + "@reactioncommerce/api-utils": "1.16.9", + "@reactioncommerce/db-version-check": "1.0.0", + "@reactioncommerce/file-collections": "0.9.3", + "@reactioncommerce/file-collections-sa-gridfs": "0.1.5", + "@reactioncommerce/logger": "1.1.5", + "@reactioncommerce/nodemailer": "5.0.5", + "@reactioncommerce/random": "1.0.2", "@snyk/protect": "latest", "graphql": "~14.7.0", "semver": "~6.3.0", From 870820b079874834d2e2d1366dda15ccff1d75e3 Mon Sep 17 00:00:00 2001 From: vanpho93 Date: Fri, 21 Oct 2022 14:46:18 +0700 Subject: [PATCH 20/33] feat: update lock file --- pnpm-lock.yaml | 135 ++++++++++++++++++++++++++++++------------------- 1 file changed, 82 insertions(+), 53 deletions(-) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 982006ee9f3..419c0a21269 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -140,53 +140,53 @@ importers: apps/reaction: specifiers: - '@reactioncommerce/api-core': ^2.0.0 - '@reactioncommerce/api-plugin-accounts': ^2.0.0 - '@reactioncommerce/api-plugin-address-validation': ^1.3.1 - '@reactioncommerce/api-plugin-address-validation-test': ^1.0.3 - '@reactioncommerce/api-plugin-authentication': ^2.2.3 - '@reactioncommerce/api-plugin-authorization-simple': ^1.3.1 - '@reactioncommerce/api-plugin-carts': ^1.2.4 - '@reactioncommerce/api-plugin-catalogs': ^1.1.2 - '@reactioncommerce/api-plugin-discounts': ^1.0.1 - '@reactioncommerce/api-plugin-discounts-codes': ^1.2.0 - '@reactioncommerce/api-plugin-email': ^1.1.0 - '@reactioncommerce/api-plugin-email-smtp': ^1.0.5 - '@reactioncommerce/api-plugin-email-templates': ^1.1.1 - '@reactioncommerce/api-plugin-files': ^1.0.19 - '@reactioncommerce/api-plugin-i18n': ^1.0.4 - '@reactioncommerce/api-plugin-inventory': ^1.0.3 - '@reactioncommerce/api-plugin-inventory-simple': ^1.0.4 - '@reactioncommerce/api-plugin-job-queue': ^1.0.2 - '@reactioncommerce/api-plugin-navigation': ^1.1.1 - '@reactioncommerce/api-plugin-notifications': ^1.1.0 - '@reactioncommerce/api-plugin-orders': ^1.4.2 - '@reactioncommerce/api-plugin-payments': ^1.0.1 - '@reactioncommerce/api-plugin-payments-example': ^1.1.1 - '@reactioncommerce/api-plugin-payments-stripe-sca': ^1.0.2 - '@reactioncommerce/api-plugin-pricing-simple': ^1.0.3 - '@reactioncommerce/api-plugin-products': ^1.0.2 - '@reactioncommerce/api-plugin-settings': ^1.0.2 - '@reactioncommerce/api-plugin-shipments': ^1.0.1 - '@reactioncommerce/api-plugin-shipments-flat-rate': ^1.0.4 - '@reactioncommerce/api-plugin-shops': ^1.2.1 - '@reactioncommerce/api-plugin-simple-schema': ^1.0.3 - '@reactioncommerce/api-plugin-sitemap-generator': ^1.2.2 - '@reactioncommerce/api-plugin-surcharges': ^1.1.7 - '@reactioncommerce/api-plugin-system-information': ^1.1.3 - '@reactioncommerce/api-plugin-tags': ^1.1.1 - '@reactioncommerce/api-plugin-taxes': ^1.1.1 - '@reactioncommerce/api-plugin-taxes-flat-rate': ^1.0.4 - '@reactioncommerce/api-plugin-translations': ^1.2.0 - '@reactioncommerce/api-utils': ^1.16.5 + '@reactioncommerce/api-core': 2.0.1 + '@reactioncommerce/api-plugin-accounts': 2.0.4 + '@reactioncommerce/api-plugin-address-validation': 1.3.3 + '@reactioncommerce/api-plugin-address-validation-test': 1.0.3 + '@reactioncommerce/api-plugin-authentication': 2.2.5 + '@reactioncommerce/api-plugin-authorization-simple': 1.3.2 + '@reactioncommerce/api-plugin-carts': 1.3.5 + '@reactioncommerce/api-plugin-catalogs': 1.1.2 + '@reactioncommerce/api-plugin-discounts': 1.0.4 + '@reactioncommerce/api-plugin-discounts-codes': 1.2.4 + '@reactioncommerce/api-plugin-email': 1.1.5 + '@reactioncommerce/api-plugin-email-smtp': 1.0.8 + '@reactioncommerce/api-plugin-email-templates': 1.1.7 + '@reactioncommerce/api-plugin-files': 1.0.19 + '@reactioncommerce/api-plugin-i18n': 1.0.6 + '@reactioncommerce/api-plugin-inventory': 1.0.5 + '@reactioncommerce/api-plugin-inventory-simple': 1.0.6 + '@reactioncommerce/api-plugin-job-queue': 1.0.7 + '@reactioncommerce/api-plugin-navigation': 1.1.2 + '@reactioncommerce/api-plugin-notifications': 1.1.4 + '@reactioncommerce/api-plugin-orders': 1.4.5 + '@reactioncommerce/api-plugin-payments': 1.0.5 + '@reactioncommerce/api-plugin-payments-example': 1.1.2 + '@reactioncommerce/api-plugin-payments-stripe-sca': 1.0.2 + '@reactioncommerce/api-plugin-pricing-simple': 1.0.7 + '@reactioncommerce/api-plugin-products': 1.3.0 + '@reactioncommerce/api-plugin-settings': 1.0.7 + '@reactioncommerce/api-plugin-shipments': 1.0.3 + '@reactioncommerce/api-plugin-shipments-flat-rate': 1.0.9 + '@reactioncommerce/api-plugin-shops': 1.3.0 + '@reactioncommerce/api-plugin-simple-schema': 1.0.3 + '@reactioncommerce/api-plugin-sitemap-generator': 1.2.3 + '@reactioncommerce/api-plugin-surcharges': 1.1.7 + '@reactioncommerce/api-plugin-system-information': 1.1.4 + '@reactioncommerce/api-plugin-tags': 1.2.0 + '@reactioncommerce/api-plugin-taxes': 1.1.3 + '@reactioncommerce/api-plugin-taxes-flat-rate': 1.0.8 + '@reactioncommerce/api-plugin-translations': 1.2.2 + '@reactioncommerce/api-utils': 1.16.9 '@reactioncommerce/data-factory': ~1.0.1 - '@reactioncommerce/db-version-check': ^1.0.0 + '@reactioncommerce/db-version-check': 1.0.0 '@reactioncommerce/eslint-config': ~2.2.0 - '@reactioncommerce/file-collections': ^0.9.3 - '@reactioncommerce/file-collections-sa-gridfs': ^0.1.5 - '@reactioncommerce/logger': ^1.1.3 - '@reactioncommerce/nodemailer': ^5.0.5 - '@reactioncommerce/random': ^1.0.2 + '@reactioncommerce/file-collections': 0.9.3 + '@reactioncommerce/file-collections-sa-gridfs': 0.1.5 + '@reactioncommerce/logger': 1.1.5 + '@reactioncommerce/nodemailer': 5.0.5 + '@reactioncommerce/random': 1.0.2 '@snyk/protect': latest apollo-link-http: ~1.5.16 apollo-server-testing: ~2.9.6 @@ -245,7 +245,7 @@ importers: '@reactioncommerce/logger': link:../../packages/logger '@reactioncommerce/nodemailer': 5.0.5 '@reactioncommerce/random': link:../../packages/random - '@snyk/protect': 1.1035.0 + '@snyk/protect': 1.1038.0 graphql: 14.7.0 semver: 6.3.0 sharp: 0.29.3 @@ -4700,7 +4700,7 @@ packages: dependencies: eslint: 8.23.1 eslint-plugin-import: 2.25.4_eslint@8.23.1 - eslint-plugin-jest: 26.9.0_2ex7m26yair3ztqnyc2u7licva + eslint-plugin-jest: 26.9.0_eslint@8.23.1 eslint-plugin-jsx-a11y: 6.5.1_eslint@8.23.1 eslint-plugin-node: 11.1.0_eslint@8.23.1 eslint-plugin-promise: 6.0.1_eslint@8.23.1 @@ -4729,8 +4729,8 @@ packages: '@sinonjs/commons': 1.8.3 dev: false - /@snyk/protect/1.1035.0: - resolution: {integrity: sha512-8s1HygVBFDmNn7HdDTslb8LyLBKdtexexl8lsbR4W1o0/JgXDxOSEztTRakVdONFOnlRNbS3DtEBvVRdHiYFVg==} + /@snyk/protect/1.1038.0: + resolution: {integrity: sha512-68GuFnFVcQz6eFwTwN9jBbiLY1OWIFL+qHwxa3TBA4+kliOPVeBrfQ49vyGR0o5WAR1ZgTWn8+3b/OLkRe2SXg==} engines: {node: '>=10'} hasBin: true dev: false @@ -5025,6 +5025,26 @@ packages: engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} dev: true + /@typescript-eslint/typescript-estree/5.37.0: + resolution: {integrity: sha512-JkFoFIt/cx59iqEDSgIGnQpCTRv96MQnXCYvJi7QhBC24uyuzbD8wVbajMB1b9x4I0octYFJ3OwjAwNqk1AjDA==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + peerDependencies: + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true + dependencies: + '@typescript-eslint/types': 5.37.0 + '@typescript-eslint/visitor-keys': 5.37.0 + debug: 4.3.4 + globby: 11.1.0 + is-glob: 4.0.3 + semver: 7.3.7 + tsutils: 3.21.0 + transitivePeerDependencies: + - supports-color + dev: true + /@typescript-eslint/typescript-estree/5.37.0_typescript@2.9.2: resolution: {integrity: sha512-JkFoFIt/cx59iqEDSgIGnQpCTRv96MQnXCYvJi7QhBC24uyuzbD8wVbajMB1b9x4I0octYFJ3OwjAwNqk1AjDA==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} @@ -5046,7 +5066,7 @@ packages: - supports-color dev: true - /@typescript-eslint/utils/5.37.0_2ex7m26yair3ztqnyc2u7licva: + /@typescript-eslint/utils/5.37.0_eslint@8.23.1: resolution: {integrity: sha512-jUEJoQrWbZhmikbcWSMDuUSxEE7ID2W/QCV/uz10WtQqfOuKZUqFGjqLJ+qhDd17rjgp+QJPqTdPIBWwoob2NQ==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} peerDependencies: @@ -5055,7 +5075,7 @@ packages: '@types/json-schema': 7.0.11 '@typescript-eslint/scope-manager': 5.37.0 '@typescript-eslint/types': 5.37.0 - '@typescript-eslint/typescript-estree': 5.37.0_typescript@2.9.2 + '@typescript-eslint/typescript-estree': 5.37.0 eslint: 8.23.1 eslint-scope: 5.1.1 eslint-utils: 3.0.0_eslint@8.23.1 @@ -7829,7 +7849,7 @@ packages: - supports-color dev: true - /eslint-plugin-jest/26.9.0_2ex7m26yair3ztqnyc2u7licva: + /eslint-plugin-jest/26.9.0_eslint@8.23.1: resolution: {integrity: sha512-TWJxWGp1J628gxh2KhaH1H1paEdgE2J61BBF1I59c6xWeL5+D1BzMxGDN/nXAfX+aSkR5u80K+XhskK6Gwq9ng==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} peerDependencies: @@ -7842,7 +7862,7 @@ packages: jest: optional: true dependencies: - '@typescript-eslint/utils': 5.37.0_2ex7m26yair3ztqnyc2u7licva + '@typescript-eslint/utils': 5.37.0_eslint@8.23.1 eslint: 8.23.1 transitivePeerDependencies: - supports-color @@ -13905,6 +13925,15 @@ packages: /tslib/2.4.0: resolution: {integrity: sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ==} + /tsutils/3.21.0: + resolution: {integrity: sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==} + engines: {node: '>= 6'} + peerDependencies: + typescript: '>=2.8.0 || >= 3.2.0-dev || >= 3.3.0-dev || >= 3.4.0-dev || >= 3.5.0-dev || >= 3.6.0-dev || >= 3.6.0-beta || >= 3.7.0-dev || >= 3.7.0-beta' + dependencies: + tslib: 1.14.1 + dev: true + /tsutils/3.21.0_typescript@2.9.2: resolution: {integrity: sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==} engines: {node: '>= 6'} From 8100fa1c61607dc8a8e5155481061e68800adbf8 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Fri, 21 Oct 2022 08:37:30 +0000 Subject: [PATCH 21/33] Version Packages --- .changeset/awaited-recursive-createHandle-call.md | 5 ----- .changeset/modern-poems-smash.md | 5 ----- apps/reaction/CHANGELOG.md | 8 ++++++++ apps/reaction/package.json | 6 +++--- packages/api-plugin-products/CHANGELOG.md | 6 ++++++ packages/api-plugin-products/package.json | 2 +- packages/api-plugin-shipments-flat-rate/CHANGELOG.md | 7 +++++++ packages/api-plugin-shipments-flat-rate/package.json | 2 +- 8 files changed, 26 insertions(+), 15 deletions(-) delete mode 100644 .changeset/awaited-recursive-createHandle-call.md delete mode 100644 .changeset/modern-poems-smash.md create mode 100644 packages/api-plugin-shipments-flat-rate/CHANGELOG.md diff --git a/.changeset/awaited-recursive-createHandle-call.md b/.changeset/awaited-recursive-createHandle-call.md deleted file mode 100644 index 02f9111c61a..00000000000 --- a/.changeset/awaited-recursive-createHandle-call.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"@reactioncommerce/api-plugin-products": patch ---- - -awaited the recursive createHandle call diff --git a/.changeset/modern-poems-smash.md b/.changeset/modern-poems-smash.md deleted file mode 100644 index 4a417e17643..00000000000 --- a/.changeset/modern-poems-smash.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"@reactioncommerce/api-plugin-shipments-flat-rate": patch ---- - -allow nested properties to be accessible for shipment restrictions. diff --git a/apps/reaction/CHANGELOG.md b/apps/reaction/CHANGELOG.md index fb3cc99437d..75cf7638cde 100644 --- a/apps/reaction/CHANGELOG.md +++ b/apps/reaction/CHANGELOG.md @@ -1,5 +1,13 @@ # reaction +## 4.2.4 + +### Patch Changes + +- Updated dependencies [[`b48825c43`](https://github.com/reactioncommerce/reaction/commit/b48825c43f1d72347e2173cf09c1a363638ae173), [`769c2185b`](https://github.com/reactioncommerce/reaction/commit/769c2185b8bb39bc0a3682b37ac8efd16aa77712)]: + - @reactioncommerce/api-plugin-products@1.3.1 + - @reactioncommerce/api-plugin-shipments-flat-rate@1.0.10 + ## 4.2.3 ### Patch Changes diff --git a/apps/reaction/package.json b/apps/reaction/package.json index 5fe143e5b8b..2d51bf54637 100644 --- a/apps/reaction/package.json +++ b/apps/reaction/package.json @@ -1,6 +1,6 @@ { "name": "reaction", - "version": "4.2.3", + "version": "4.2.4", "description": "Reaction is a modern reactive, real-time event driven ecommerce platform.", "main": "./src/index.js", "type": "module", @@ -45,10 +45,10 @@ "@reactioncommerce/api-plugin-payments-example": "1.1.2", "@reactioncommerce/api-plugin-payments-stripe-sca": "1.0.2", "@reactioncommerce/api-plugin-pricing-simple": "1.0.7", - "@reactioncommerce/api-plugin-products": "1.3.0", + "@reactioncommerce/api-plugin-products": "1.3.1", "@reactioncommerce/api-plugin-settings": "1.0.7", "@reactioncommerce/api-plugin-shipments": "1.0.3", - "@reactioncommerce/api-plugin-shipments-flat-rate": "1.0.9", + "@reactioncommerce/api-plugin-shipments-flat-rate": "1.0.10", "@reactioncommerce/api-plugin-shops": "1.3.0", "@reactioncommerce/api-plugin-simple-schema": "1.0.3", "@reactioncommerce/api-plugin-sitemap-generator": "1.2.3", diff --git a/packages/api-plugin-products/CHANGELOG.md b/packages/api-plugin-products/CHANGELOG.md index f784ebc878d..97b1d4698ef 100644 --- a/packages/api-plugin-products/CHANGELOG.md +++ b/packages/api-plugin-products/CHANGELOG.md @@ -1,5 +1,11 @@ # @reactioncommerce/api-plugin-products +## 1.3.1 + +### Patch Changes + +- [#6565](https://github.com/reactioncommerce/reaction/pull/6565) [`b48825c43`](https://github.com/reactioncommerce/reaction/commit/b48825c43f1d72347e2173cf09c1a363638ae173) Thanks [@smriti0710](https://github.com/smriti0710)! - awaited the recursive createHandle call + ## 1.3.0 ### Minor Changes diff --git a/packages/api-plugin-products/package.json b/packages/api-plugin-products/package.json index a7cd417ed9b..e61f74406ad 100644 --- a/packages/api-plugin-products/package.json +++ b/packages/api-plugin-products/package.json @@ -1,7 +1,7 @@ { "name": "@reactioncommerce/api-plugin-products", "description": "Products plugin for the Reaction API", - "version": "1.3.0", + "version": "1.3.1", "main": "index.js", "type": "module", "engines": { diff --git a/packages/api-plugin-shipments-flat-rate/CHANGELOG.md b/packages/api-plugin-shipments-flat-rate/CHANGELOG.md new file mode 100644 index 00000000000..77e45efab64 --- /dev/null +++ b/packages/api-plugin-shipments-flat-rate/CHANGELOG.md @@ -0,0 +1,7 @@ +# @reactioncommerce/api-plugin-shipments-flat-rate + +## 1.0.10 + +### Patch Changes + +- [#6578](https://github.com/reactioncommerce/reaction/pull/6578) [`769c2185b`](https://github.com/reactioncommerce/reaction/commit/769c2185b8bb39bc0a3682b37ac8efd16aa77712) Thanks [@range123](https://github.com/range123)! - allow nested properties to be accessible for shipment restrictions. diff --git a/packages/api-plugin-shipments-flat-rate/package.json b/packages/api-plugin-shipments-flat-rate/package.json index 7b284aae128..8b436dd82f3 100644 --- a/packages/api-plugin-shipments-flat-rate/package.json +++ b/packages/api-plugin-shipments-flat-rate/package.json @@ -1,7 +1,7 @@ { "name": "@reactioncommerce/api-plugin-shipments-flat-rate", "description": "Flat Rate Shipments plugin for the Reaction API", - "version": "1.0.9", + "version": "1.0.10", "main": "index.js", "type": "module", "engines": { From 1eb6b0718c87b5ba706feea4a76ffd7ba8cfe008 Mon Sep 17 00:00:00 2001 From: zenweasel Date: Fri, 21 Oct 2022 08:38:07 +0000 Subject: [PATCH 22/33] feat: update pnpm-lock --- pnpm-lock.yaml | 43 +++++++------------------------------------ 1 file changed, 7 insertions(+), 36 deletions(-) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 419c0a21269..b127fc4ae0a 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -165,10 +165,10 @@ importers: '@reactioncommerce/api-plugin-payments-example': 1.1.2 '@reactioncommerce/api-plugin-payments-stripe-sca': 1.0.2 '@reactioncommerce/api-plugin-pricing-simple': 1.0.7 - '@reactioncommerce/api-plugin-products': 1.3.0 + '@reactioncommerce/api-plugin-products': 1.3.1 '@reactioncommerce/api-plugin-settings': 1.0.7 '@reactioncommerce/api-plugin-shipments': 1.0.3 - '@reactioncommerce/api-plugin-shipments-flat-rate': 1.0.9 + '@reactioncommerce/api-plugin-shipments-flat-rate': 1.0.10 '@reactioncommerce/api-plugin-shops': 1.3.0 '@reactioncommerce/api-plugin-simple-schema': 1.0.3 '@reactioncommerce/api-plugin-sitemap-generator': 1.2.3 @@ -4700,7 +4700,7 @@ packages: dependencies: eslint: 8.23.1 eslint-plugin-import: 2.25.4_eslint@8.23.1 - eslint-plugin-jest: 26.9.0_eslint@8.23.1 + eslint-plugin-jest: 26.9.0_2ex7m26yair3ztqnyc2u7licva eslint-plugin-jsx-a11y: 6.5.1_eslint@8.23.1 eslint-plugin-node: 11.1.0_eslint@8.23.1 eslint-plugin-promise: 6.0.1_eslint@8.23.1 @@ -5025,26 +5025,6 @@ packages: engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} dev: true - /@typescript-eslint/typescript-estree/5.37.0: - resolution: {integrity: sha512-JkFoFIt/cx59iqEDSgIGnQpCTRv96MQnXCYvJi7QhBC24uyuzbD8wVbajMB1b9x4I0octYFJ3OwjAwNqk1AjDA==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - peerDependencies: - typescript: '*' - peerDependenciesMeta: - typescript: - optional: true - dependencies: - '@typescript-eslint/types': 5.37.0 - '@typescript-eslint/visitor-keys': 5.37.0 - debug: 4.3.4 - globby: 11.1.0 - is-glob: 4.0.3 - semver: 7.3.7 - tsutils: 3.21.0 - transitivePeerDependencies: - - supports-color - dev: true - /@typescript-eslint/typescript-estree/5.37.0_typescript@2.9.2: resolution: {integrity: sha512-JkFoFIt/cx59iqEDSgIGnQpCTRv96MQnXCYvJi7QhBC24uyuzbD8wVbajMB1b9x4I0octYFJ3OwjAwNqk1AjDA==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} @@ -5066,7 +5046,7 @@ packages: - supports-color dev: true - /@typescript-eslint/utils/5.37.0_eslint@8.23.1: + /@typescript-eslint/utils/5.37.0_2ex7m26yair3ztqnyc2u7licva: resolution: {integrity: sha512-jUEJoQrWbZhmikbcWSMDuUSxEE7ID2W/QCV/uz10WtQqfOuKZUqFGjqLJ+qhDd17rjgp+QJPqTdPIBWwoob2NQ==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} peerDependencies: @@ -5075,7 +5055,7 @@ packages: '@types/json-schema': 7.0.11 '@typescript-eslint/scope-manager': 5.37.0 '@typescript-eslint/types': 5.37.0 - '@typescript-eslint/typescript-estree': 5.37.0 + '@typescript-eslint/typescript-estree': 5.37.0_typescript@2.9.2 eslint: 8.23.1 eslint-scope: 5.1.1 eslint-utils: 3.0.0_eslint@8.23.1 @@ -7849,7 +7829,7 @@ packages: - supports-color dev: true - /eslint-plugin-jest/26.9.0_eslint@8.23.1: + /eslint-plugin-jest/26.9.0_2ex7m26yair3ztqnyc2u7licva: resolution: {integrity: sha512-TWJxWGp1J628gxh2KhaH1H1paEdgE2J61BBF1I59c6xWeL5+D1BzMxGDN/nXAfX+aSkR5u80K+XhskK6Gwq9ng==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} peerDependencies: @@ -7862,7 +7842,7 @@ packages: jest: optional: true dependencies: - '@typescript-eslint/utils': 5.37.0_eslint@8.23.1 + '@typescript-eslint/utils': 5.37.0_2ex7m26yair3ztqnyc2u7licva eslint: 8.23.1 transitivePeerDependencies: - supports-color @@ -13925,15 +13905,6 @@ packages: /tslib/2.4.0: resolution: {integrity: sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ==} - /tsutils/3.21.0: - resolution: {integrity: sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==} - engines: {node: '>= 6'} - peerDependencies: - typescript: '>=2.8.0 || >= 3.2.0-dev || >= 3.3.0-dev || >= 3.4.0-dev || >= 3.5.0-dev || >= 3.6.0-dev || >= 3.6.0-beta || >= 3.7.0-dev || >= 3.7.0-beta' - dependencies: - tslib: 1.14.1 - dev: true - /tsutils/3.21.0_typescript@2.9.2: resolution: {integrity: sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==} engines: {node: '>= 6'} From 599cd1328ef6ba769ab81dd37d5b6b9b05e63c32 Mon Sep 17 00:00:00 2001 From: tuanvu0995 Date: Mon, 24 Oct 2022 17:20:29 +0700 Subject: [PATCH 23/33] feat: add replaceOne to mockCollection Signed-off-by: tuanvu0995 --- packages/api-utils/lib/tests/mockCollection.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/api-utils/lib/tests/mockCollection.js b/packages/api-utils/lib/tests/mockCollection.js index 4546d344e4a..07c5be79bca 100644 --- a/packages/api-utils/lib/tests/mockCollection.js +++ b/packages/api-utils/lib/tests/mockCollection.js @@ -40,6 +40,7 @@ export default function mockCollection(collectionName) { matchedCount: 1, modifiedCount: 1 })), - updateMany: jest.fn().mockName(`${collectionName}.updateMany`) + updateMany: jest.fn().mockName(`${collectionName}.updateMany`), + replaceOne: jest.fn().mockName(`${collectionName}.replaceOne`) }; } From b0425c6fca6a8f98ee9519226c3604c241adeca1 Mon Sep 17 00:00:00 2001 From: Vu Lai <85773378+tuanvu0995@users.noreply.github.com> Date: Mon, 24 Oct 2022 20:50:27 +0700 Subject: [PATCH 24/33] Create shaggy-feet-look.md Signed-off-by: tuanvu0995 --- .changeset/shaggy-feet-look.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/shaggy-feet-look.md diff --git a/.changeset/shaggy-feet-look.md b/.changeset/shaggy-feet-look.md new file mode 100644 index 00000000000..bfb1d2a5c1e --- /dev/null +++ b/.changeset/shaggy-feet-look.md @@ -0,0 +1,5 @@ +--- +"@reactioncommerce/api-utils": patch +--- + +feat: add replaceOne to mockCollection From f0a0fa7a2615534dd6f62407f4b45e5b4b690154 Mon Sep 17 00:00:00 2001 From: Vu Lai <85773378+tuanvu0995@users.noreply.github.com> Date: Tue, 25 Oct 2022 08:22:01 +0700 Subject: [PATCH 25/33] Update shaggy-feet-look.md Signed-off-by: tuanvu0995 --- .changeset/shaggy-feet-look.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.changeset/shaggy-feet-look.md b/.changeset/shaggy-feet-look.md index bfb1d2a5c1e..dcb2fd43aa2 100644 --- a/.changeset/shaggy-feet-look.md +++ b/.changeset/shaggy-feet-look.md @@ -1,5 +1,5 @@ --- -"@reactioncommerce/api-utils": patch +"@reactioncommerce/api-utils": minor --- feat: add replaceOne to mockCollection From b5ab314d6a42c2d9c7db0fc6df3dd923323cbfc4 Mon Sep 17 00:00:00 2001 From: tuanvu0995 Date: Tue, 25 Oct 2022 08:36:49 +0700 Subject: [PATCH 26/33] feat: change changeset message Signed-off-by: tuanvu0995 --- .changeset/shaggy-feet-look.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.changeset/shaggy-feet-look.md b/.changeset/shaggy-feet-look.md index dcb2fd43aa2..f9bf494d528 100644 --- a/.changeset/shaggy-feet-look.md +++ b/.changeset/shaggy-feet-look.md @@ -2,4 +2,4 @@ "@reactioncommerce/api-utils": minor --- -feat: add replaceOne to mockCollection +feat: add replaceOne mock function to mockCollection in the api-untils plugin From b2cc11b76ba63e16c44098f077bc52162da7f8b9 Mon Sep 17 00:00:00 2001 From: Brent Hoover Date: Thu, 27 Oct 2022 16:16:07 +0800 Subject: [PATCH 27/33] Create SECURITY.md Signed-off-by: Brent Hoover --- SECURITY.md | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) create mode 100644 SECURITY.md diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 00000000000..b88b38921f4 --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,16 @@ +# Security Policy + +## Supported Versions + +Use this section to tell people about which versions of your project are +currently being supported with security updates. + +| Version | Supported | +| ------- | ------------------ | +| 4.x.x | :white_check_mark: | +| < 4.0 | :x: | + +## Reporting a Vulnerability +Should you discover a vulnerability, you should not open an issue. Please email +[hello-open-commerce@mailchimp.com](mailto:hello-open-commerce@mailchimp.com). We will work with you directly +and correct the vulnerability and then make an annoucement once a release is available. From 10e623f55d764f10845ddaf214714ba7a0cd04e8 Mon Sep 17 00:00:00 2001 From: Brent Hoover Date: Fri, 28 Oct 2022 19:28:50 +0800 Subject: [PATCH 28/33] Update SECURITY.md Signed-off-by: Brent Hoover --- SECURITY.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/SECURITY.md b/SECURITY.md index b88b38921f4..a26173ab585 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -2,8 +2,7 @@ ## Supported Versions -Use this section to tell people about which versions of your project are -currently being supported with security updates. +These are the versions currently supported with security updates. | Version | Supported | | ------- | ------------------ | @@ -11,6 +10,7 @@ currently being supported with security updates. | < 4.0 | :x: | ## Reporting a Vulnerability + Should you discover a vulnerability, you should not open an issue. Please email [hello-open-commerce@mailchimp.com](mailto:hello-open-commerce@mailchimp.com). We will work with you directly -and correct the vulnerability and then make an annoucement once a release is available. +and correct the vulnerability and then make an announcement once a release is available. From 9a6c933c2a37593051c0fb8e0fa32e8cb7f5040e Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Fri, 28 Oct 2022 07:51:53 +0000 Subject: [PATCH 29/33] Version Packages Signed-off-by: Brent Hoover --- .changeset/shaggy-feet-look.md | 5 ----- .changeset/updateSurcharge-return-full-item.md | 5 ----- apps/reaction/CHANGELOG.md | 8 ++++++++ apps/reaction/package.json | 6 +++--- packages/api-plugin-surcharges/CHANGELOG.md | 9 +++++++++ packages/api-plugin-surcharges/package.json | 4 ++-- packages/api-utils/CHANGELOG.md | 7 +++++++ packages/api-utils/package.json | 2 +- 8 files changed, 30 insertions(+), 16 deletions(-) delete mode 100644 .changeset/shaggy-feet-look.md delete mode 100644 .changeset/updateSurcharge-return-full-item.md create mode 100644 packages/api-utils/CHANGELOG.md diff --git a/.changeset/shaggy-feet-look.md b/.changeset/shaggy-feet-look.md deleted file mode 100644 index f9bf494d528..00000000000 --- a/.changeset/shaggy-feet-look.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"@reactioncommerce/api-utils": minor ---- - -feat: add replaceOne mock function to mockCollection in the api-untils plugin diff --git a/.changeset/updateSurcharge-return-full-item.md b/.changeset/updateSurcharge-return-full-item.md deleted file mode 100644 index c6e247b4a42..00000000000 --- a/.changeset/updateSurcharge-return-full-item.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"@reactioncommerce/api-plugin-surcharges": patch ---- - -Fixed Update surcharge mutation to return the whole item from db diff --git a/apps/reaction/CHANGELOG.md b/apps/reaction/CHANGELOG.md index 75cf7638cde..cfd1f62511d 100644 --- a/apps/reaction/CHANGELOG.md +++ b/apps/reaction/CHANGELOG.md @@ -1,5 +1,13 @@ # reaction +## 4.2.5 + +### Patch Changes + +- Updated dependencies [[`8c5645764`](https://github.com/reactioncommerce/reaction/commit/8c5645764a746ce4171747072eacfe87bf62abe3), [`bb93339fb`](https://github.com/reactioncommerce/reaction/commit/bb93339fb4da5b1cb131a9e0bf50e502433d519d)]: + - @reactioncommerce/api-utils@1.17.0 + - @reactioncommerce/api-plugin-surcharges@1.1.8 + ## 4.2.4 ### Patch Changes diff --git a/apps/reaction/package.json b/apps/reaction/package.json index 2d51bf54637..34304e1b44a 100644 --- a/apps/reaction/package.json +++ b/apps/reaction/package.json @@ -1,6 +1,6 @@ { "name": "reaction", - "version": "4.2.4", + "version": "4.2.5", "description": "Reaction is a modern reactive, real-time event driven ecommerce platform.", "main": "./src/index.js", "type": "module", @@ -52,13 +52,13 @@ "@reactioncommerce/api-plugin-shops": "1.3.0", "@reactioncommerce/api-plugin-simple-schema": "1.0.3", "@reactioncommerce/api-plugin-sitemap-generator": "1.2.3", - "@reactioncommerce/api-plugin-surcharges": "1.1.7", + "@reactioncommerce/api-plugin-surcharges": "1.1.8", "@reactioncommerce/api-plugin-system-information": "1.1.4", "@reactioncommerce/api-plugin-tags": "1.2.0", "@reactioncommerce/api-plugin-taxes": "1.1.3", "@reactioncommerce/api-plugin-taxes-flat-rate": "1.0.8", "@reactioncommerce/api-plugin-translations": "1.2.2", - "@reactioncommerce/api-utils": "1.16.9", + "@reactioncommerce/api-utils": "1.17.0", "@reactioncommerce/db-version-check": "1.0.0", "@reactioncommerce/file-collections": "0.9.3", "@reactioncommerce/file-collections-sa-gridfs": "0.1.5", diff --git a/packages/api-plugin-surcharges/CHANGELOG.md b/packages/api-plugin-surcharges/CHANGELOG.md index 60b16fb7fff..8dd52a19104 100644 --- a/packages/api-plugin-surcharges/CHANGELOG.md +++ b/packages/api-plugin-surcharges/CHANGELOG.md @@ -1,5 +1,14 @@ # @reactioncommerce/api-plugin-surcharges +## 1.1.8 + +### Patch Changes + +- [#6576](https://github.com/reactioncommerce/reaction/pull/6576) [`bb93339fb`](https://github.com/reactioncommerce/reaction/commit/bb93339fb4da5b1cb131a9e0bf50e502433d519d) Thanks [@sushmitha-malae](https://github.com/sushmitha-malae)! - Fixed Update surcharge mutation to return the whole item from db + +- Updated dependencies [[`8c5645764`](https://github.com/reactioncommerce/reaction/commit/8c5645764a746ce4171747072eacfe87bf62abe3)]: + - @reactioncommerce/api-utils@1.17.0 + ## 1.1.7 ### Patch Changes diff --git a/packages/api-plugin-surcharges/package.json b/packages/api-plugin-surcharges/package.json index e8fbe6b6367..0615210d779 100644 --- a/packages/api-plugin-surcharges/package.json +++ b/packages/api-plugin-surcharges/package.json @@ -1,7 +1,7 @@ { "name": "@reactioncommerce/api-plugin-surcharges", "description": "Surcharges plugin for the Reaction API", - "version": "1.1.7", + "version": "1.1.8", "main": "index.js", "type": "module", "engines": { @@ -26,7 +26,7 @@ }, "sideEffects": false, "dependencies": { - "@reactioncommerce/api-utils": "^1.16.9", + "@reactioncommerce/api-utils": "^1.17.0", "@reactioncommerce/random": "^1.0.2", "@reactioncommerce/reaction-error": "^1.0.1", "simpl-schema": "^1.12.0" diff --git a/packages/api-utils/CHANGELOG.md b/packages/api-utils/CHANGELOG.md new file mode 100644 index 00000000000..b7d0fc04972 --- /dev/null +++ b/packages/api-utils/CHANGELOG.md @@ -0,0 +1,7 @@ +# @reactioncommerce/api-utils + +## 1.17.0 + +### Minor Changes + +- [#6592](https://github.com/reactioncommerce/reaction/pull/6592) [`8c5645764`](https://github.com/reactioncommerce/reaction/commit/8c5645764a746ce4171747072eacfe87bf62abe3) Thanks [@tuanvu0995](https://github.com/tuanvu0995)! - feat: add replaceOne mock function to mockCollection in the api-untils plugin diff --git a/packages/api-utils/package.json b/packages/api-utils/package.json index 9b80dbf72eb..dd05a82aaba 100644 --- a/packages/api-utils/package.json +++ b/packages/api-utils/package.json @@ -1,7 +1,7 @@ { "name": "@reactioncommerce/api-utils", "description": "Utility functions for the Reaction API", - "version": "1.16.9", + "version": "1.17.0", "main": "index.js", "type": "module", "engines": { From dab552c82cdd0fd49bc44c564a7056b4996c09e7 Mon Sep 17 00:00:00 2001 From: zenweasel Date: Fri, 28 Oct 2022 07:52:25 +0000 Subject: [PATCH 30/33] feat: update pnpm-lock Signed-off-by: Brent Hoover --- pnpm-lock.yaml | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index b127fc4ae0a..a54914d1c58 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -172,13 +172,13 @@ importers: '@reactioncommerce/api-plugin-shops': 1.3.0 '@reactioncommerce/api-plugin-simple-schema': 1.0.3 '@reactioncommerce/api-plugin-sitemap-generator': 1.2.3 - '@reactioncommerce/api-plugin-surcharges': 1.1.7 + '@reactioncommerce/api-plugin-surcharges': 1.1.8 '@reactioncommerce/api-plugin-system-information': 1.1.4 '@reactioncommerce/api-plugin-tags': 1.2.0 '@reactioncommerce/api-plugin-taxes': 1.1.3 '@reactioncommerce/api-plugin-taxes-flat-rate': 1.0.8 '@reactioncommerce/api-plugin-translations': 1.2.2 - '@reactioncommerce/api-utils': 1.16.9 + '@reactioncommerce/api-utils': 1.17.0 '@reactioncommerce/data-factory': ~1.0.1 '@reactioncommerce/db-version-check': 1.0.0 '@reactioncommerce/eslint-config': ~2.2.0 @@ -245,7 +245,7 @@ importers: '@reactioncommerce/logger': link:../../packages/logger '@reactioncommerce/nodemailer': 5.0.5 '@reactioncommerce/random': link:../../packages/random - '@snyk/protect': 1.1038.0 + '@snyk/protect': 1.1044.0 graphql: 14.7.0 semver: 6.3.0 sharp: 0.29.3 @@ -1224,7 +1224,7 @@ importers: specifiers: '@babel/core': ^7.7.7 '@babel/preset-env': ^7.7.7 - '@reactioncommerce/api-utils': ^1.16.9 + '@reactioncommerce/api-utils': ^1.17.0 '@reactioncommerce/babel-remove-es-create-require': ~1.0.0 '@reactioncommerce/data-factory': ~1.0.1 '@reactioncommerce/random': ^1.0.2 @@ -4729,8 +4729,8 @@ packages: '@sinonjs/commons': 1.8.3 dev: false - /@snyk/protect/1.1038.0: - resolution: {integrity: sha512-68GuFnFVcQz6eFwTwN9jBbiLY1OWIFL+qHwxa3TBA4+kliOPVeBrfQ49vyGR0o5WAR1ZgTWn8+3b/OLkRe2SXg==} + /@snyk/protect/1.1044.0: + resolution: {integrity: sha512-Wi6zmOMsyM2FRlxvqLo3opf7SDvcpWWR3RGJVHPVg6uh7VByAYrKome1zl8WRUaBr4qfEpL0jJLFKaBkHYUlAg==} engines: {node: '>=10'} hasBin: true dev: false From 1d04e2dd703d38f8a067c7091034a7898234fc15 Mon Sep 17 00:00:00 2001 From: Brian Nguyen Date: Mon, 24 Oct 2022 11:24:00 +0700 Subject: [PATCH 31/33] fix: issue copy release from reaction@4.x.x to v4.x.x Signed-off-by: Brian Nguyen Signed-off-by: Brent Hoover --- .github/workflows/tagging-and-release.yml | 53 ++++++++++------------- 1 file changed, 24 insertions(+), 29 deletions(-) diff --git a/.github/workflows/tagging-and-release.yml b/.github/workflows/tagging-and-release.yml index 30f2dbd575f..fe0478e6ab3 100644 --- a/.github/workflows/tagging-and-release.yml +++ b/.github/workflows/tagging-and-release.yml @@ -22,7 +22,6 @@ jobs: run: | git config --global user.name "$(git --no-pager log --format=format:'%an' -n 1)" git config --global user.email "$(git --no-pager log --format=format:'%ae' -n 1)" - - name: Use Node.js 14.x uses: actions/setup-node@v2 with: @@ -32,14 +31,6 @@ jobs: run: | npm i -g pnpm@latest pnpm install -r --ignore-scripts - - - name: Release apps and packages - uses: changesets/action@v1 - with: - publish: pnpm changeset tag - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - name: Get tag version id: get-tag-version run: | @@ -47,9 +38,9 @@ jobs: REACTION_APP_NEW_TAG=v$VERSION echo "REACTION_APP_NEW_TAG=$REACTION_APP_NEW_TAG" >> $GITHUB_ENV echo '::set-output name=newTag::'$REACTION_APP_NEW_TAG + echo '::set-output name=version::'$VERSION echo "New release tag is $REACTION_APP_NEW_TAG" - - - name: Check should create release tag + - name: Check should create release id: should-create-tag run: | if [ $(git tag -l "$REACTION_APP_NEW_TAG") ]; then @@ -57,33 +48,37 @@ jobs: else echo '::set-output name=REACTION_APP_TAG_EXISTS::false' fi - - - name: Create release tag + - name: Release apps and packages if: steps.should-create-tag.outputs.REACTION_APP_TAG_EXISTS == 'false' - run: | - git tag -a $REACTION_APP_NEW_TAG -m "chore(release): $REACTION_APP_NEW_TAG [skip ci]" - git push origin $REACTION_APP_NEW_TAG - echo "Create release tag success: $REACTION_APP_NEW_TAG" - - - name: Push release tag - run: git push origin $REACTION_APP_NEW_TAG + uses: changesets/action@v1 + with: + publish: pnpm changeset tag + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - name: Extract release notes if: steps.should-create-tag.outputs.REACTION_APP_TAG_EXISTS == 'false' id: extract-release-notes - uses: ffurrer2/extract-release-notes@v1 + uses: cardinalby/git-get-release-action@v1 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} with: - changelog_file: apps/reaction/CHANGELOG.md + tag: "reaction@${{ steps.get-tag-version.outputs.version }}" + doNotFailIfNotFound: true + + - name: Create tag + if: steps.extract-release-notes.outputs.body != '' + run: | + git tag -a $REACTION_APP_NEW_TAG -m "chore(release): $REACTION_APP_NEW_TAG [skip ci]" + git push origin $REACTION_APP_NEW_TAG + echo "Create tag success: $REACTION_APP_NEW_TAG" - name: Create Release - id: create_release - if: steps.should-create-tag.outputs.REACTION_APP_TAG_EXISTS == 'false' - uses: actions/create-release@latest - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # This token is provided by Actions, you do not need to create your own token + if: steps.extract-release-notes.outputs.body != '' + uses: softprops/action-gh-release@v1 with: + token: ${{ secrets.GITHUB_TOKEN }} + body: ${{ steps.extract-release-notes.outputs.body }} tag_name: ${{ steps.get-tag-version.outputs.newTag }} - release_name: ${{ steps.get-tag-version.outputs.newTag }} - body: ${{ steps.extract-release-notes.outputs.release_notes }} draft: false prerelease: false From 5996940e2810adc3104ff7054378325ac5a6332f Mon Sep 17 00:00:00 2001 From: skodamarthi Date: Fri, 28 Oct 2022 15:04:41 +0100 Subject: [PATCH 32/33] update docs to remove references to semantic release Signed-off-by: skodamarthi --- .github/pull_request_template.md | 2 +- packages/api-plugin-address-validation-test/README.md | 1 - packages/api-plugin-address-validation/README.md | 1 - packages/api-plugin-authentication/README.md | 1 - packages/api-plugin-authorization-simple/README.md | 1 - packages/api-plugin-carts/README.md | 1 - packages/api-plugin-catalogs/README.md | 1 - packages/api-plugin-discounts-codes/README.md | 1 - packages/api-plugin-discounts/README.md | 1 - packages/api-plugin-email-smtp/README.md | 1 - packages/api-plugin-email-templates/README.md | 1 - packages/api-plugin-email/README.md | 1 - packages/api-plugin-files/README.md | 1 - packages/api-plugin-i18n/README.md | 1 - packages/api-plugin-inventory-simple/README.md | 1 - packages/api-plugin-inventory/README.md | 1 - packages/api-plugin-job-queue/README.md | 1 - packages/api-plugin-navigation/README.md | 1 - packages/api-plugin-notifications/README.md | 1 - packages/api-plugin-orders/README.md | 1 - packages/api-plugin-payments-stripe-sca/README.md | 1 - packages/api-plugin-sample-data/README.md | 5 ++--- packages/api-utils/README.md | 4 ---- packages/db-version-check/README.md | 4 +--- 24 files changed, 4 insertions(+), 31 deletions(-) diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index 30a94f14879..7e80904dda0 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -26,4 +26,4 @@ Note any work that you did to mitigate the effect of any breaking changes such a 2. Assume that testers already know how to start the app, and do the basic setup tasks. 3. Be detailed enough that someone can work through it without being too granular -More detail for what each of these sections should include are available in our [Contributing Docs](CONTRIBUTING.md). This project uses [semantic-release](https://semantic-release.gitbook.io/semantic-release/), please use their [commit message format.](https://semantic-release.gitbook.io/semantic-release/#commit-message-format). +More detail for what each of these sections should include are available in our [Contributing Docs](CONTRIBUTING.md). diff --git a/packages/api-plugin-address-validation-test/README.md b/packages/api-plugin-address-validation-test/README.md index 542af255b09..70a0ce42bb9 100644 --- a/packages/api-plugin-address-validation-test/README.md +++ b/packages/api-plugin-address-validation-test/README.md @@ -2,7 +2,6 @@ [![npm (scoped)](https://img.shields.io/npm/v/@reactioncommerce/api-plugin-address-validation-test.svg)](https://www.npmjs.com/package/@reactioncommerce/api-plugin-address-validation-test) [![CircleCI](https://circleci.com/gh/reactioncommerce/api-plugin-address-validation-test.svg?style=svg)](https://circleci.com/gh/reactioncommerce/api-plugin-address-validation-test) -[![semantic-release](https://img.shields.io/badge/%20%20%F0%9F%93%A6%F0%9F%9A%80-semantic--release-e10079.svg)](https://github.com/semantic-release/semantic-release) ## Summary diff --git a/packages/api-plugin-address-validation/README.md b/packages/api-plugin-address-validation/README.md index 9b4ed31ca00..5af8f4e4257 100644 --- a/packages/api-plugin-address-validation/README.md +++ b/packages/api-plugin-address-validation/README.md @@ -2,7 +2,6 @@ [![npm (scoped)](https://img.shields.io/npm/v/@reactioncommerce/api-plugin-address-validation.svg)](https://www.npmjs.com/package/@reactioncommerce/api-plugin-address-validation) [![CircleCI](https://circleci.com/gh/reactioncommerce/api-plugin-address-validation.svg?style=svg)](https://circleci.com/gh/reactioncommerce/api-plugin-address-validation) -[![semantic-release](https://img.shields.io/badge/%20%20%F0%9F%93%A6%F0%9F%9A%80-semantic--release-e10079.svg)](https://github.com/semantic-release/semantic-release) ## Summary diff --git a/packages/api-plugin-authentication/README.md b/packages/api-plugin-authentication/README.md index 0ac93dc16da..ffa7dfcd60b 100644 --- a/packages/api-plugin-authentication/README.md +++ b/packages/api-plugin-authentication/README.md @@ -2,7 +2,6 @@ [![npm (scoped)](https://img.shields.io/npm/v/@reactioncommerce/api-plugin-authentication.svg)](https://www.npmjs.com/package/@reactioncommerce/api-plugin-authentication) [![CircleCI](https://circleci.com/gh/reactioncommerce/api-plugin-authentication.svg?style=svg)](https://circleci.com/gh/reactioncommerce/api-plugin-authentication) -[![semantic-release](https://img.shields.io/badge/%20%20%F0%9F%93%A6%F0%9F%9A%80-semantic--release-e10079.svg)](https://github.com/semantic-release/semantic-release) ## Summary diff --git a/packages/api-plugin-authorization-simple/README.md b/packages/api-plugin-authorization-simple/README.md index 0e53b0c7f12..337b519be86 100644 --- a/packages/api-plugin-authorization-simple/README.md +++ b/packages/api-plugin-authorization-simple/README.md @@ -2,7 +2,6 @@ [![npm (scoped)](https://img.shields.io/npm/v/@reactioncommerce/api-plugin-authorization-simple.svg)](https://www.npmjs.com/package/@reactioncommerce/api-plugin-authorization-simple) [![CircleCI](https://circleci.com/gh/reactioncommerce/api-plugin-authorization-simple.svg?style=svg)](https://circleci.com/gh/reactioncommerce/api-plugin-authorization-simple) -[![semantic-release](https://img.shields.io/badge/%20%20%F0%9F%93%A6%F0%9F%9A%80-semantic--release-e10079.svg)](https://github.com/semantic-release/semantic-release) ## Summary diff --git a/packages/api-plugin-carts/README.md b/packages/api-plugin-carts/README.md index afefbca2ade..f415df2cd6c 100644 --- a/packages/api-plugin-carts/README.md +++ b/packages/api-plugin-carts/README.md @@ -2,7 +2,6 @@ [![npm (scoped)](https://img.shields.io/npm/v/@reactioncommerce/api-plugin-carts.svg)](https://www.npmjs.com/package/@reactioncommerce/api-plugin-carts) [![CircleCI](https://circleci.com/gh/reactioncommerce/api-plugin-carts.svg?style=svg)](https://circleci.com/gh/reactioncommerce/api-plugin-carts) -[![semantic-release](https://img.shields.io/badge/%20%20%F0%9F%93%A6%F0%9F%9A%80-semantic--release-e10079.svg)](https://github.com/semantic-release/semantic-release) ## Summary diff --git a/packages/api-plugin-catalogs/README.md b/packages/api-plugin-catalogs/README.md index 307fc38558e..e2b01f696c7 100644 --- a/packages/api-plugin-catalogs/README.md +++ b/packages/api-plugin-catalogs/README.md @@ -2,7 +2,6 @@ [![npm (scoped)](https://img.shields.io/npm/v/@reactioncommerce/api-plugin-catalogs.svg)](https://www.npmjs.com/package/@reactioncommerce/api-plugin-catalogs) [![CircleCI](https://circleci.com/gh/reactioncommerce/api-plugin-catalogs.svg?style=svg)](https://circleci.com/gh/reactioncommerce/api-plugin-catalogs) -[![semantic-release](https://img.shields.io/badge/%20%20%F0%9F%93%A6%F0%9F%9A%80-semantic--release-e10079.svg)](https://github.com/semantic-release/semantic-release) ## Summary diff --git a/packages/api-plugin-discounts-codes/README.md b/packages/api-plugin-discounts-codes/README.md index 718b64313fd..d8111a2ed59 100644 --- a/packages/api-plugin-discounts-codes/README.md +++ b/packages/api-plugin-discounts-codes/README.md @@ -2,7 +2,6 @@ [![npm (scoped)](https://img.shields.io/npm/v/@reactioncommerce/api-plugin-discounts-codes.svg)](https://www.npmjs.com/package/@reactioncommerce/api-plugin-discounts-codes) [![CircleCI](https://circleci.com/gh/reactioncommerce/api-plugin-discounts-codes.svg?style=svg)](https://circleci.com/gh/reactioncommerce/api-plugin-discounts-codes) -[![semantic-release](https://img.shields.io/badge/%20%20%F0%9F%93%A6%F0%9F%9A%80-semantic--release-e10079.svg)](https://github.com/semantic-release/semantic-release) ## Summary diff --git a/packages/api-plugin-discounts/README.md b/packages/api-plugin-discounts/README.md index 01bd047770f..b10022a405f 100644 --- a/packages/api-plugin-discounts/README.md +++ b/packages/api-plugin-discounts/README.md @@ -2,7 +2,6 @@ [![npm (scoped)](https://img.shields.io/npm/v/@reactioncommerce/api-plugin-discounts.svg)](https://www.npmjs.com/package/@reactioncommerce/api-plugin-discounts) [![CircleCI](https://circleci.com/gh/reactioncommerce/api-plugin-discounts.svg?style=svg)](https://circleci.com/gh/reactioncommerce/api-plugin-discounts) -[![semantic-release](https://img.shields.io/badge/%20%20%F0%9F%93%A6%F0%9F%9A%80-semantic--release-e10079.svg)](https://github.com/semantic-release/semantic-release) ## Summary diff --git a/packages/api-plugin-email-smtp/README.md b/packages/api-plugin-email-smtp/README.md index 68a0727a386..d3120ba0083 100644 --- a/packages/api-plugin-email-smtp/README.md +++ b/packages/api-plugin-email-smtp/README.md @@ -2,7 +2,6 @@ [![npm (scoped)](https://img.shields.io/npm/v/@reactioncommerce/api-plugin-email-smtp.svg)](https://www.npmjs.com/package/@reactioncommerce/api-plugin-email-smtp) [![CircleCI](https://circleci.com/gh/reactioncommerce/api-plugin-email-smtp.svg?style=svg)](https://circleci.com/gh/reactioncommerce/api-plugin-email-smtp) -[![semantic-release](https://img.shields.io/badge/%20%20%F0%9F%93%A6%F0%9F%9A%80-semantic--release-e10079.svg)](https://github.com/semantic-release/semantic-release) ## Summary diff --git a/packages/api-plugin-email-templates/README.md b/packages/api-plugin-email-templates/README.md index 52071bb63b1..80fda087311 100644 --- a/packages/api-plugin-email-templates/README.md +++ b/packages/api-plugin-email-templates/README.md @@ -2,7 +2,6 @@ [![npm (scoped)](https://img.shields.io/npm/v/@reactioncommerce/api-plugin-email-templates.svg)](https://www.npmjs.com/package/@reactioncommerce/api-plugin-email-templates) [![CircleCI](https://circleci.com/gh/reactioncommerce/api-plugin-email-templates.svg?style=svg)](https://circleci.com/gh/reactioncommerce/api-plugin-email-templates) -[![semantic-release](https://img.shields.io/badge/%20%20%F0%9F%93%A6%F0%9F%9A%80-semantic--release-e10079.svg)](https://github.com/semantic-release/semantic-release) ## Summary diff --git a/packages/api-plugin-email/README.md b/packages/api-plugin-email/README.md index a8fcc2cd95a..bd894fa000e 100644 --- a/packages/api-plugin-email/README.md +++ b/packages/api-plugin-email/README.md @@ -2,7 +2,6 @@ [![npm (scoped)](https://img.shields.io/npm/v/@reactioncommerce/api-plugin-email.svg)](https://www.npmjs.com/package/@reactioncommerce/api-plugin-email) [![CircleCI](https://circleci.com/gh/reactioncommerce/api-plugin-email.svg?style=svg)](https://circleci.com/gh/reactioncommerce/api-plugin-email) -[![semantic-release](https://img.shields.io/badge/%20%20%F0%9F%93%A6%F0%9F%9A%80-semantic--release-e10079.svg)](https://github.com/semantic-release/semantic-release) ## Summary diff --git a/packages/api-plugin-files/README.md b/packages/api-plugin-files/README.md index 1cba7911c98..db0c6d09ce4 100644 --- a/packages/api-plugin-files/README.md +++ b/packages/api-plugin-files/README.md @@ -2,7 +2,6 @@ [![npm (scoped)](https://img.shields.io/npm/v/@reactioncommerce/api-plugin-files.svg)](https://www.npmjs.com/package/@reactioncommerce/api-plugin-files) [![CircleCI](https://circleci.com/gh/reactioncommerce/api-plugin-files.svg?style=svg)](https://circleci.com/gh/reactioncommerce/api-plugin-files) -[![semantic-release](https://img.shields.io/badge/%20%20%F0%9F%93%A6%F0%9F%9A%80-semantic--release-e10079.svg)](https://github.com/semantic-release/semantic-release) ## Summary diff --git a/packages/api-plugin-i18n/README.md b/packages/api-plugin-i18n/README.md index 673a11eeae0..a5e9aab6eca 100644 --- a/packages/api-plugin-i18n/README.md +++ b/packages/api-plugin-i18n/README.md @@ -2,7 +2,6 @@ [![npm (scoped)](https://img.shields.io/npm/v/@reactioncommerce/api-plugin-i18n.svg)](https://www.npmjs.com/package/@reactioncommerce/api-plugin-i18n) [![CircleCI](https://circleci.com/gh/reactioncommerce/api-plugin-i18n.svg?style=svg)](https://circleci.com/gh/reactioncommerce/api-plugin-i18n) -[![semantic-release](https://img.shields.io/badge/%20%20%F0%9F%93%A6%F0%9F%9A%80-semantic--release-e10079.svg)](https://github.com/semantic-release/semantic-release) ## Summary diff --git a/packages/api-plugin-inventory-simple/README.md b/packages/api-plugin-inventory-simple/README.md index 4ee1b338485..73773a5b554 100644 --- a/packages/api-plugin-inventory-simple/README.md +++ b/packages/api-plugin-inventory-simple/README.md @@ -2,7 +2,6 @@ [![npm (scoped)](https://img.shields.io/npm/v/@reactioncommerce/api-plugin-inventory-simple.svg)](https://www.npmjs.com/package/@reactioncommerce/api-plugin-inventory-simple) [![CircleCI](https://circleci.com/gh/reactioncommerce/api-plugin-inventory-simple.svg?style=svg)](https://circleci.com/gh/reactioncommerce/api-plugin-inventory-simple) -[![semantic-release](https://img.shields.io/badge/%20%20%F0%9F%93%A6%F0%9F%9A%80-semantic--release-e10079.svg)](https://github.com/semantic-release/semantic-release) ## Summary diff --git a/packages/api-plugin-inventory/README.md b/packages/api-plugin-inventory/README.md index eef3869b87e..1b6cd51df01 100644 --- a/packages/api-plugin-inventory/README.md +++ b/packages/api-plugin-inventory/README.md @@ -2,7 +2,6 @@ [![npm (scoped)](https://img.shields.io/npm/v/@reactioncommerce/api-plugin-inventory.svg)](https://www.npmjs.com/package/@reactioncommerce/api-plugin-inventory) [![CircleCI](https://circleci.com/gh/reactioncommerce/api-plugin-inventory.svg?style=svg)](https://circleci.com/gh/reactioncommerce/api-plugin-inventory) -[![semantic-release](https://img.shields.io/badge/%20%20%F0%9F%93%A6%F0%9F%9A%80-semantic--release-e10079.svg)](https://github.com/semantic-release/semantic-release) ## Summary diff --git a/packages/api-plugin-job-queue/README.md b/packages/api-plugin-job-queue/README.md index c0ced3bcea6..bd55d9f2cc7 100644 --- a/packages/api-plugin-job-queue/README.md +++ b/packages/api-plugin-job-queue/README.md @@ -2,7 +2,6 @@ [![npm (scoped)](https://img.shields.io/npm/v/@reactioncommerce/api-plugin-job-queue.svg)](https://www.npmjs.com/package/@reactioncommerce/api-plugin-job-queue) [![CircleCI](https://circleci.com/gh/reactioncommerce/api-plugin-job-queue.svg?style=svg)](https://circleci.com/gh/reactioncommerce/api-plugin-job-queue) -[![semantic-release](https://img.shields.io/badge/%20%20%F0%9F%93%A6%F0%9F%9A%80-semantic--release-e10079.svg)](https://github.com/semantic-release/semantic-release) ## Summary diff --git a/packages/api-plugin-navigation/README.md b/packages/api-plugin-navigation/README.md index 128f8f4001e..c86c68a5a78 100644 --- a/packages/api-plugin-navigation/README.md +++ b/packages/api-plugin-navigation/README.md @@ -2,7 +2,6 @@ [![npm (scoped)](https://img.shields.io/npm/v/@reactioncommerce/api-plugin-navigation.svg)](https://www.npmjs.com/package/@reactioncommerce/api-plugin-navigation) [![CircleCI](https://circleci.com/gh/reactioncommerce/api-plugin-navigation.svg?style=svg)](https://circleci.com/gh/reactioncommerce/api-plugin-navigation) -[![semantic-release](https://img.shields.io/badge/%20%20%F0%9F%93%A6%F0%9F%9A%80-semantic--release-e10079.svg)](https://github.com/semantic-release/semantic-release) ## Summary diff --git a/packages/api-plugin-notifications/README.md b/packages/api-plugin-notifications/README.md index 2441abb2067..7031c6f314e 100644 --- a/packages/api-plugin-notifications/README.md +++ b/packages/api-plugin-notifications/README.md @@ -2,7 +2,6 @@ [![npm (scoped)](https://img.shields.io/npm/v/@reactioncommerce/api-plugin-notifications.svg)](https://www.npmjs.com/package/@reactioncommerce/api-plugin-notifications) [![CircleCI](https://circleci.com/gh/reactioncommerce/api-plugin-notifications.svg?style=svg)](https://circleci.com/gh/reactioncommerce/api-plugin-notifications) -[![semantic-release](https://img.shields.io/badge/%20%20%F0%9F%93%A6%F0%9F%9A%80-semantic--release-e10079.svg)](https://github.com/semantic-release/semantic-release) ## Summary diff --git a/packages/api-plugin-orders/README.md b/packages/api-plugin-orders/README.md index 08f1c960fda..1d2ea415b7d 100644 --- a/packages/api-plugin-orders/README.md +++ b/packages/api-plugin-orders/README.md @@ -2,7 +2,6 @@ [![npm (scoped)](https://img.shields.io/npm/v/@reactioncommerce/api-plugin-orders.svg)](https://www.npmjs.com/package/@reactioncommerce/api-plugin-orders) [![CircleCI](https://circleci.com/gh/reactioncommerce/api-plugin-orders.svg?style=svg)](https://circleci.com/gh/reactioncommerce/api-plugin-orders) -[![semantic-release](https://img.shields.io/badge/%20%20%F0%9F%93%A6%F0%9F%9A%80-semantic--release-e10079.svg)](https://github.com/semantic-release/semantic-release) ## Summary diff --git a/packages/api-plugin-payments-stripe-sca/README.md b/packages/api-plugin-payments-stripe-sca/README.md index b1408550dc3..da7c83b9ee8 100644 --- a/packages/api-plugin-payments-stripe-sca/README.md +++ b/packages/api-plugin-payments-stripe-sca/README.md @@ -2,7 +2,6 @@ [![npm (scoped)](https://img.shields.io/npm/v/@reactioncommerce/api-plugin-payments-stripe-sca.svg)](https://www.npmjs.com/package/@reactioncommerce/api-plugin-payments-stripe-sca) [![CircleCI](https://circleci.com/gh/reactioncommerce/api-plugin-payments-stripe-sca.svg?style=svg)](https://circleci.com/gh/reactioncommerce/api-plugin-payments-stripe-sca) -[![semantic-release](https://img.shields.io/badge/%20%20%F0%9F%93%A6%F0%9F%9A%80-semantic--release-e10079.svg)](https://github.com/semantic-release/semantic-release) ## Summary diff --git a/packages/api-plugin-sample-data/README.md b/packages/api-plugin-sample-data/README.md index 00094200214..3f7911e11e9 100644 --- a/packages/api-plugin-sample-data/README.md +++ b/packages/api-plugin-sample-data/README.md @@ -2,7 +2,6 @@ [![npm (scoped)](https://img.shields.io/npm/v/@reactioncommerce/api-plugin-example.svg)](https://www.npmjs.com/package/@reactioncommerce/api-plugin-example) [![CircleCI](https://circleci.com/gh/reactioncommerce/api-plugin-example.svg?style=svg)](https://circleci.com/gh/reactioncommerce/api-plugin-example) -[![semantic-release](https://img.shields.io/badge/%20%20%F0%9F%93%A6%F0%9F%9A%80-semantic--release-e10079.svg)](https://github.com/semantic-release/semantic-release) ## Summary @@ -386,7 +385,7 @@ password: password ### `.circleci/` -Adds CI scripts that enable Circle CI to run tests, lint, and semantic release your project. The `semantic-release` portions of the script are commented out, and should be uncommented in a PR once your plugin is ready to be released. +Adds CI scripts that enable Circle CI to run tests and lint your project. ### `src/` @@ -414,7 +413,7 @@ If your plugin uses `Apache 2` licensing, you can leave this file as-is. If anot ### `package.json` -The provided `package.json` is set up to install all needed packages and config for linting, testing, and semantic-release. You'll need to update the `name`, `description`, and add any new dependencies your plugin files use. +The provided `package.json` is set up to install all needed packages and config for linting and testing. You'll need to update the `name`, `description`, and add any new dependencies your plugin files use. ### `index.js` diff --git a/packages/api-utils/README.md b/packages/api-utils/README.md index 8ddddf48b38..4b9c4ad159e 100644 --- a/packages/api-utils/README.md +++ b/packages/api-utils/README.md @@ -2,7 +2,6 @@ [![npm (scoped)](https://img.shields.io/npm/v/@reactioncommerce/api-utils.svg)](https://www.npmjs.com/package/@reactioncommerce/api-utils) [![CircleCI](https://circleci.com/gh/reactioncommerce/api-utils.svg?style=svg)](https://circleci.com/gh/reactioncommerce/api-utils) -[![semantic-release](https://img.shields.io/badge/%20%20%F0%9F%93%A6%F0%9F%9A%80-semantic--release-e10079.svg)](https://github.com/semantic-release/semantic-release) Utility functions for the Reaction API. @@ -27,6 +26,3 @@ Refer to [package docs](https://github.com/reactioncommerce/api-utils/tree/trunk ## Releases This NPM package is published automatically on every push to the `trunk` branch. Be sure to use proper Git commit messages so that the version will be bumped properly and release notes can be automatically generated. - -- Refer to https://github.com/semantic-release/semantic-release#commit-message-format -- To avoid triggering a release, such as for a README-only change, include `[skip release]` in your commit message. diff --git a/packages/db-version-check/README.md b/packages/db-version-check/README.md index 1332465c8c5..1b69e4a05d9 100644 --- a/packages/db-version-check/README.md +++ b/packages/db-version-check/README.md @@ -44,11 +44,9 @@ if (!ok) { To ensure that all contributors follow the correct message convention, each time you commit your message will be validated with the [commitlint](https://www.npmjs.com/package/@commitlint/cli) package, enabled by the [husky](https://www.npmjs.com/package/husky) Git hooks manager. -Examples of commit messages: https://github.com/semantic-release/semantic-release - ## Publication to NPM -The `@reactioncommerce/db-version-check` package is automatically published by CI when commits are merged or pushed to the `trunk` branch. This is done using [semantic-release](https://www.npmjs.com/package/semantic-release), which also determines version bumps based on conventional Git commit messages. +The `@reactioncommerce/db-version-check` package is automatically published by CI when commits are merged or pushed to the `trunk` branch. ## Developer Certificate of Origin We use the [Developer Certificate of Origin (DCO)](https://developercertificate.org/) in lieu of a Contributor License Agreement for all contributions to Reaction Commerce open source projects. We request that contributors agree to the terms of the DCO and indicate that agreement by signing-off all commits made to Reaction Commerce projects by adding a line with your name and email address to every Git commit message contributed: From 36b7a5c389425c77eae1fb091777f0b36656c784 Mon Sep 17 00:00:00 2001 From: Sujith Date: Tue, 1 Nov 2022 00:12:04 +0530 Subject: [PATCH 33/33] fix: fixed broken test Signed-off-by: Sujith --- .../selectFulfillmentOptionForGroup.test.js | 30 +++++++------- .../updateFulfillmentOptionsForGroup.test.js | 41 ++++++++++--------- 2 files changed, 37 insertions(+), 34 deletions(-) diff --git a/packages/api-plugin-fulfillment/src/mutations/selectFulfillmentOptionForGroup.test.js b/packages/api-plugin-fulfillment/src/mutations/selectFulfillmentOptionForGroup.test.js index 9b6e362af3a..a526864913d 100644 --- a/packages/api-plugin-fulfillment/src/mutations/selectFulfillmentOptionForGroup.test.js +++ b/packages/api-plugin-fulfillment/src/mutations/selectFulfillmentOptionForGroup.test.js @@ -1,20 +1,22 @@ import mockContext from "@reactioncommerce/api-utils/tests/mockContext.js"; import selectFulfillmentOptionForGroup from "./selectFulfillmentOptionForGroup.js"; -mockContext.queries.getCartById = jest.fn().mockName("getCartById").mockReturnValueOnce(Promise.resolve({ - _id: "cartId", - shipping: [{ - _id: "group1", - itemIds: ["123"], - shipmentQuotes: [{ - rate: 0, - method: { - _id: "valid-method" - } - }], - type: "shipping" - }] -})); +beforeEach(() => { + mockContext.queries.getCartById = jest.fn().mockName("getCartById").mockReturnValueOnce(Promise.resolve({ + _id: "cartId", + shipping: [{ + _id: "group1", + itemIds: ["123"], + shipmentQuotes: [{ + rate: 0, + method: { + _id: "valid-method" + } + }], + type: "shipping" + }] + })); +}); beforeAll(() => { if (!mockContext.mutations.saveCart) { diff --git a/packages/api-plugin-fulfillment/src/mutations/updateFulfillmentOptionsForGroup.test.js b/packages/api-plugin-fulfillment/src/mutations/updateFulfillmentOptionsForGroup.test.js index ce97fd76b18..557637fa626 100644 --- a/packages/api-plugin-fulfillment/src/mutations/updateFulfillmentOptionsForGroup.test.js +++ b/packages/api-plugin-fulfillment/src/mutations/updateFulfillmentOptionsForGroup.test.js @@ -2,26 +2,6 @@ import mockContext from "@reactioncommerce/api-utils/tests/mockContext.js"; import Factory from "../tests/factory.js"; import updateFulfillmentOptionsForGroup from "./updateFulfillmentOptionsForGroup.js"; -mockContext.queries.getCartById = jest.fn().mockName("getCartById").mockReturnValueOnce(Promise.resolve({ - _id: "cartId", - items: [{ - _id: "123", - price: { - amount: 19.99 - }, - priceWhenAdded: { - amount: 19.99 - }, - subtotal: { - amount: 19.99 - } - }], - shipping: [{ - _id: "group1", - itemIds: ["123"], - type: "shipping" - }] -})); const fakeCart = Factory.Cart.makeOne(); const fakeQuote = Factory.ShipmentQuote.makeOne(); @@ -43,6 +23,27 @@ beforeAll(() => { beforeEach(() => { mockGetFulfillmentMethodsWithQuotes.mockClear(); + + mockContext.queries.getCartById = jest.fn().mockName("getCartById").mockReturnValueOnce(Promise.resolve({ + _id: "cartId", + items: [{ + _id: "123", + price: { + amount: 19.99 + }, + priceWhenAdded: { + amount: 19.99 + }, + subtotal: { + amount: 19.99 + } + }], + shipping: [{ + _id: "group1", + itemIds: ["123"], + type: "shipping" + }] + })); }); test("updates cart properly for empty rates", async () => {