From 6be616a68232b65715bad2ef85d9ef9e518e3884 Mon Sep 17 00:00:00 2001 From: vanpho93 Date: Mon, 27 Feb 2023 21:20:02 +0700 Subject: [PATCH 1/3] feat: add additional redeemed coupon information Signed-off-by: vanpho93 --- .../src/index.js | 1 + .../src/queries/couponLog.js | 12 ++ .../src/queries/couponLogByOrderId.js | 11 ++ .../src/queries/couponLogs.js | 34 ++++++ .../src/queries/index.js | 8 +- .../src/resolvers/Order/index.js | 3 + .../src/resolvers/Query/couponLog.js | 16 +++ .../src/resolvers/Query/couponLogs.js | 23 ++++ .../src/resolvers/Query/index.js | 6 +- .../src/resolvers/index.js | 2 + .../src/schemas/schema.graphql | 113 ++++++++++++++++++ .../src/simpleSchemas.js | 1 + .../src/utils/updateOrderCoupon.js | 6 +- pnpm-lock.yaml | 7 +- 14 files changed, 236 insertions(+), 7 deletions(-) create mode 100644 packages/api-plugin-promotions-coupons/src/queries/couponLog.js create mode 100644 packages/api-plugin-promotions-coupons/src/queries/couponLogByOrderId.js create mode 100644 packages/api-plugin-promotions-coupons/src/queries/couponLogs.js create mode 100644 packages/api-plugin-promotions-coupons/src/resolvers/Order/index.js create mode 100644 packages/api-plugin-promotions-coupons/src/resolvers/Query/couponLog.js create mode 100644 packages/api-plugin-promotions-coupons/src/resolvers/Query/couponLogs.js diff --git a/packages/api-plugin-promotions-coupons/src/index.js b/packages/api-plugin-promotions-coupons/src/index.js index 8d709799550..c720d335917 100644 --- a/packages/api-plugin-promotions-coupons/src/index.js +++ b/packages/api-plugin-promotions-coupons/src/index.js @@ -33,6 +33,7 @@ export default async function register(app) { name: "CouponLogs", indexes: [ [{ couponId: 1 }], + [{ orderId: 1 }], [{ promotionId: 1 }], [{ couponId: 1, accountId: 1 }, { unique: true }] ] diff --git a/packages/api-plugin-promotions-coupons/src/queries/couponLog.js b/packages/api-plugin-promotions-coupons/src/queries/couponLog.js new file mode 100644 index 00000000000..137a10945ae --- /dev/null +++ b/packages/api-plugin-promotions-coupons/src/queries/couponLog.js @@ -0,0 +1,12 @@ +/** + * @summary return a single coupon log based on shopId and _id + * @param {Object} context - the application context + * @param {String} shopId - The id of the shop + * @param {String} _id - The unencoded id of the coupon log + * @return {Object} - The coupon log or null + */ +export default async function couponLog(context, { shopId, _id }) { + const { collections: { CouponLogs } } = context; + const singleCouponLog = await CouponLogs.findOne({ shopId, _id }); + return singleCouponLog; +} diff --git a/packages/api-plugin-promotions-coupons/src/queries/couponLogByOrderId.js b/packages/api-plugin-promotions-coupons/src/queries/couponLogByOrderId.js new file mode 100644 index 00000000000..3f72e51b249 --- /dev/null +++ b/packages/api-plugin-promotions-coupons/src/queries/couponLogByOrderId.js @@ -0,0 +1,11 @@ +/** + * @summary return a single coupon log based on shopId and _id + * @param {Object} context - the application context + * @param {String} params.orderId - The order id of the coupon log + * @return {Object} - The coupon log or null + */ +export default async function couponLogByOrderId(context, { orderId }) { + const { collections: { CouponLogs } } = context; + const singleCouponLog = await CouponLogs.findOne({ orderId }); + return singleCouponLog; +} diff --git a/packages/api-plugin-promotions-coupons/src/queries/couponLogs.js b/packages/api-plugin-promotions-coupons/src/queries/couponLogs.js new file mode 100644 index 00000000000..954f61044de --- /dev/null +++ b/packages/api-plugin-promotions-coupons/src/queries/couponLogs.js @@ -0,0 +1,34 @@ +/** + * @summary return a possibly filtered list of coupon logs + * @param {Object} context - The application context + * @param {String} shopId - The shopId to query for + * @param {Object} filter - optional filter parameters + * @return {Promise>} - A list of coupon logs + */ +export default async function couponLogs(context, shopId, filter) { + const { collections: { CouponLogs } } = context; + + const selector = { shopId }; + + if (filter) { + const { couponId, promotionId, orderId, accountId } = filter; + + if (couponId) { + selector.couponId = couponId; + } + + if (promotionId) { + selector.promotionId = promotionId; + } + + if (orderId) { + selector.orderId = orderId; + } + + if (accountId) { + selector.accountId = accountId; + } + } + + return CouponLogs.find(selector); +} diff --git a/packages/api-plugin-promotions-coupons/src/queries/index.js b/packages/api-plugin-promotions-coupons/src/queries/index.js index 4ab1be71056..c93d840ddf7 100644 --- a/packages/api-plugin-promotions-coupons/src/queries/index.js +++ b/packages/api-plugin-promotions-coupons/src/queries/index.js @@ -1,7 +1,13 @@ import coupon from "./coupon.js"; import coupons from "./coupons.js"; +import couponLog from "./couponLog.js"; +import couponLogs from "./couponLogs.js"; +import couponLogByOrderId from "./couponLogByOrderId.js"; export default { coupon, - coupons + coupons, + couponLog, + couponLogs, + couponLogByOrderId }; diff --git a/packages/api-plugin-promotions-coupons/src/resolvers/Order/index.js b/packages/api-plugin-promotions-coupons/src/resolvers/Order/index.js new file mode 100644 index 00000000000..950909dd78c --- /dev/null +++ b/packages/api-plugin-promotions-coupons/src/resolvers/Order/index.js @@ -0,0 +1,3 @@ +export default { + couponLog: (order, _, context) => context.queries.couponLogByOrderId(context, order.orderId) +}; diff --git a/packages/api-plugin-promotions-coupons/src/resolvers/Query/couponLog.js b/packages/api-plugin-promotions-coupons/src/resolvers/Query/couponLog.js new file mode 100644 index 00000000000..13c9578c75d --- /dev/null +++ b/packages/api-plugin-promotions-coupons/src/resolvers/Query/couponLog.js @@ -0,0 +1,16 @@ +/** + * @summary query the coupons collection for a single coupon log + * @param {Object} _ - unused + * @param {Object} args - an object of all arguments that were sent by the client + * @param {String} args.shopId - Shop id of the coupon + * @param {Object} context - an object containing the per-request state + * @returns {Promise} A coupon log record or null + */ +export default async function couponLog(_, args, context) { + const { input } = args; + const { shopId, _id } = input; + await context.validatePermissions("reaction:legacy:promotions", "read", { shopId }); + return context.queries.couponLog(context, { + shopId, _id + }); +} diff --git a/packages/api-plugin-promotions-coupons/src/resolvers/Query/couponLogs.js b/packages/api-plugin-promotions-coupons/src/resolvers/Query/couponLogs.js new file mode 100644 index 00000000000..f5001125093 --- /dev/null +++ b/packages/api-plugin-promotions-coupons/src/resolvers/Query/couponLogs.js @@ -0,0 +1,23 @@ +import getPaginatedResponse from "@reactioncommerce/api-utils/graphql/getPaginatedResponse.js"; +import wasFieldRequested from "@reactioncommerce/api-utils/graphql/wasFieldRequested.js"; + +/** + * @summary Query for a list of coupon logs + * @param {Object} _ - unused + * @param {Object} args - an object of all arguments that were sent by the client + * @param {String} args.shopId - id of user to query + * @param {Object} context - an object containing the per-request state + * @param {Object} info Info about the GraphQL request + * @returns {Promise} CouponLogs + */ +export default async function couponLogs(_, args, context, info) { + const { shopId, filter, ...connectionArgs } = args; + await context.validatePermissions("reaction:legacy:promotions", "read", { shopId }); + const query = await context.queries.couponLogs(context, shopId, filter); + + return getPaginatedResponse(query, connectionArgs, { + includeHasNextPage: wasFieldRequested("pageInfo.hasNextPage", info), + includeHasPreviousPage: wasFieldRequested("pageInfo.hasPreviousPage", info), + includeTotalCount: wasFieldRequested("totalCount", info) + }); +} diff --git a/packages/api-plugin-promotions-coupons/src/resolvers/Query/index.js b/packages/api-plugin-promotions-coupons/src/resolvers/Query/index.js index 4ab1be71056..6f990a26698 100644 --- a/packages/api-plugin-promotions-coupons/src/resolvers/Query/index.js +++ b/packages/api-plugin-promotions-coupons/src/resolvers/Query/index.js @@ -1,7 +1,11 @@ import coupon from "./coupon.js"; import coupons from "./coupons.js"; +import couponLog from "./couponLog.js"; +import couponLogs from "./couponLogs.js"; export default { coupon, - coupons + coupons, + couponLog, + couponLogs }; diff --git a/packages/api-plugin-promotions-coupons/src/resolvers/index.js b/packages/api-plugin-promotions-coupons/src/resolvers/index.js index aeec9a3729b..af9fe0af669 100644 --- a/packages/api-plugin-promotions-coupons/src/resolvers/index.js +++ b/packages/api-plugin-promotions-coupons/src/resolvers/index.js @@ -1,8 +1,10 @@ import Promotion from "./Promotion/index.js"; import Mutation from "./Mutation/index.js"; import Query from "./Query/index.js"; +import Order from "./Order/index.js"; export default { + Order, Promotion, Mutation, Query diff --git a/packages/api-plugin-promotions-coupons/src/schemas/schema.graphql b/packages/api-plugin-promotions-coupons/src/schemas/schema.graphql index b64040034f0..f4860c35bd3 100644 --- a/packages/api-plugin-promotions-coupons/src/schemas/schema.graphql +++ b/packages/api-plugin-promotions-coupons/src/schemas/schema.graphql @@ -42,11 +42,44 @@ type Coupon { discountId: ID } +type CouponLog { + _id: ID! + + "The shop ID" + shopId: ID! + + "The coupon ID" + couponId: ID! + + "The order ID" + orderId: ID + + "The promotion ID" + promotionId: ID! + + "The coupon owner ID" + accountId: ID + + "The coupon code" + usedCount: Int + + "The time the coupon was used" + createdAt: Date + + "The log details for each time the coupon was used" + usedLogs: [JSONObject] +} + extend type Promotion { "The coupon code" coupon: Coupon } +extend type Order { + "The coupon log for this order that was applied" + couponLog: CouponLog +} + "Input for the applyCouponToCart mutation" input ApplyCouponToCartInput { @@ -138,6 +171,28 @@ input CouponFilter { isArchived: Boolean } +input CouponLogQueryInput { + "The unique ID of the coupon log" + _id: String! + + "The unique ID of the shop" + shopId: String! +} + +input CouponLogFilter { + "The coupon ID" + couponId: ID + + "The related promotion ID" + promotionId: ID + + "The orderId" + orderId: ID + + "The account ID of the user who is applying the coupon" + accountId: ID +} + "Input for the removeCouponFromCart mutation" input RemoveCouponFromCartInput { @@ -206,6 +261,32 @@ type CouponConnection { totalCount: Int! } +"A connection edge in which each node is a `CouponLog` object" +type CouponLogEdge { + "The cursor that represents this node in the paginated results" + cursor: ConnectionCursor! + + "The coupon log node" + node: CouponLog +} + +type CouponLogConnection { + "The list of nodes that match the query, wrapped in an edge to provide a cursor string for each" + edges: [CouponEdge] + + """ + 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: [CouponLog] + + "Information to help a client request the next or previous page" + pageInfo: PageInfo! + + "The total number of nodes that match your query" + totalCount: Int! +} + extend type Query { "Get a coupon" coupon( @@ -238,6 +319,38 @@ extend type Query { sortOrder: String ): CouponConnection + + "Get a coupon log" + couponLog( + input: CouponLogQueryInput + ): CouponLog + + "Get list of coupon logs" + couponLogs( + "The coupon 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 + + filter: CouponLogFilter + + sortBy: String + + sortOrder: String + ): CouponLogConnection } extend type Mutation { diff --git a/packages/api-plugin-promotions-coupons/src/simpleSchemas.js b/packages/api-plugin-promotions-coupons/src/simpleSchemas.js index 227d602f9d0..316b6865360 100644 --- a/packages/api-plugin-promotions-coupons/src/simpleSchemas.js +++ b/packages/api-plugin-promotions-coupons/src/simpleSchemas.js @@ -71,6 +71,7 @@ export const Coupon = new SimpleSchema({ export const CouponLog = new SimpleSchema({ "_id": String, + "shopId": String, "couponId": String, "promotionId": String, "orderId": { diff --git a/packages/api-plugin-promotions-coupons/src/utils/updateOrderCoupon.js b/packages/api-plugin-promotions-coupons/src/utils/updateOrderCoupon.js index 6a8eb2df87c..4e0a1e233ac 100644 --- a/packages/api-plugin-promotions-coupons/src/utils/updateOrderCoupon.js +++ b/packages/api-plugin-promotions-coupons/src/utils/updateOrderCoupon.js @@ -44,11 +44,13 @@ export default async function updateOrderCoupon(context, order) { if (!couponLog) { await CouponLogs.insertOne({ _id: Random.id(), + shopId: order.shopId, couponId, + orderId: order._id, promotionId: promotion._id, accountId: order.accountId, - createdAt: new Date(), - usedCount: 1 + usedCount: 1, + createdAt: new Date() }); continue; } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 62f37138709..f49182080cd 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -258,7 +258,7 @@ importers: '@reactioncommerce/file-collections-sa-gridfs': link:../../packages/file-collections-sa-gridfs '@reactioncommerce/logger': link:../../packages/logger '@reactioncommerce/random': link:../../packages/random - '@snyk/protect': 1.1096.0 + '@snyk/protect': 1.1105.0 graphql: 16.6.0 nodemailer: 6.8.0 semver: 6.3.0 @@ -5151,8 +5151,8 @@ packages: '@sinonjs/commons': 1.8.3 dev: false - /@snyk/protect/1.1096.0: - resolution: {integrity: sha512-E0hkw5TY8rIygL2uohywBrW72f1x/g36mHdMxS9UzLB9DHLDudJJYHMwJfdjl6dW7cuuTVauv8TDQireMkjOVw==} + /@snyk/protect/1.1105.0: + resolution: {integrity: sha512-wIRSrm7DcIqpi6JPEKsxenpSXOBj+z5sCUGN0O9YBZV57FYBxhlkOS0I9k6hvKhUmzcPeQ2zbgmGCTbOzhc6zw==} engines: {node: '>=10'} hasBin: true dev: false @@ -14222,6 +14222,7 @@ packages: /tslib/2.4.1: resolution: {integrity: sha512-tGyy4dAjRIEwI7BzsB0lynWgOpfqjUdq91XXAlIWD2OwKBH7oCl/GZG/HT4BOHrTlPMOASlMQ7veyTqpmRcrNA==} + dev: false /tsutils/3.21.0_typescript@2.9.2: resolution: {integrity: sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==} From dc51fd5f19961e870486d3cfee4e998017bb6c22 Mon Sep 17 00:00:00 2001 From: vanpho93 Date: Mon, 27 Feb 2023 21:23:00 +0700 Subject: [PATCH 2/3] fix: revert snyk Signed-off-by: vanpho93 --- pnpm-lock.yaml | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index f49182080cd..62f37138709 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -258,7 +258,7 @@ importers: '@reactioncommerce/file-collections-sa-gridfs': link:../../packages/file-collections-sa-gridfs '@reactioncommerce/logger': link:../../packages/logger '@reactioncommerce/random': link:../../packages/random - '@snyk/protect': 1.1105.0 + '@snyk/protect': 1.1096.0 graphql: 16.6.0 nodemailer: 6.8.0 semver: 6.3.0 @@ -5151,8 +5151,8 @@ packages: '@sinonjs/commons': 1.8.3 dev: false - /@snyk/protect/1.1105.0: - resolution: {integrity: sha512-wIRSrm7DcIqpi6JPEKsxenpSXOBj+z5sCUGN0O9YBZV57FYBxhlkOS0I9k6hvKhUmzcPeQ2zbgmGCTbOzhc6zw==} + /@snyk/protect/1.1096.0: + resolution: {integrity: sha512-E0hkw5TY8rIygL2uohywBrW72f1x/g36mHdMxS9UzLB9DHLDudJJYHMwJfdjl6dW7cuuTVauv8TDQireMkjOVw==} engines: {node: '>=10'} hasBin: true dev: false @@ -14222,7 +14222,6 @@ packages: /tslib/2.4.1: resolution: {integrity: sha512-tGyy4dAjRIEwI7BzsB0lynWgOpfqjUdq91XXAlIWD2OwKBH7oCl/GZG/HT4BOHrTlPMOASlMQ7veyTqpmRcrNA==} - dev: false /tsutils/3.21.0_typescript@2.9.2: resolution: {integrity: sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==} From c88826baaad0d101b29f03551d33171c630b42bf Mon Sep 17 00:00:00 2001 From: vanpho93 Date: Tue, 28 Feb 2023 14:56:38 +0700 Subject: [PATCH 3/3] feat: remove usedLogs field on CouponLog schema Signed-off-by: vanpho93 --- .../api-plugin-promotions-coupons/src/schemas/schema.graphql | 3 --- 1 file changed, 3 deletions(-) diff --git a/packages/api-plugin-promotions-coupons/src/schemas/schema.graphql b/packages/api-plugin-promotions-coupons/src/schemas/schema.graphql index f4860c35bd3..05ba787973d 100644 --- a/packages/api-plugin-promotions-coupons/src/schemas/schema.graphql +++ b/packages/api-plugin-promotions-coupons/src/schemas/schema.graphql @@ -65,9 +65,6 @@ type CouponLog { "The time the coupon was used" createdAt: Date - - "The log details for each time the coupon was used" - usedLogs: [JSONObject] } extend type Promotion {