From fdbf11ac7a10f1a9d41a2654cd0e20a5dc12fda1 Mon Sep 17 00:00:00 2001 From: Sebastian Rindom Date: Wed, 7 Apr 2021 11:00:24 +0200 Subject: [PATCH] docs: describes usage of plugin --- .../README.md | 89 ++++++++++++++++--- .../src/api/routes/add-email.js | 2 +- .../src/models/restock-notification.ts | 22 +++++ .../__tests__/restock-notification.js | 10 +-- .../src/services/restock-notification.js | 25 ++++++ .../src/services/sendgrid.js | 21 ++++- .../src/subscribers/order.js | 4 - .../src/subscribers/restock.js | 41 +++++++++ 8 files changed, 188 insertions(+), 26 deletions(-) create mode 100644 packages/medusa-plugin-sendgrid/src/subscribers/restock.js diff --git a/packages/medusa-plugin-restock-notification/README.md b/packages/medusa-plugin-restock-notification/README.md index f507c9d491..d63861df30 100644 --- a/packages/medusa-plugin-restock-notification/README.md +++ b/packages/medusa-plugin-restock-notification/README.md @@ -1,24 +1,89 @@ # medusa-plugin-restock-notification -Twilio SMS / Messaging plugin. -## Plugin Options +## Usage -``` -{ - account_sid: [twilio messaging account sid] (required), - auth_token: [twilio massaging authentication token] (required), - from_number: [the number used as sender SMS], +Install the plugin: + +`$ yarn add medusa-plugin-restock-notification` + +```js +// medusa-config.js + +module.exports = { + ..., + plugins: [ + ..., + `medusa-plugin-restock-notification` + ] } ``` -## Dynamic usage +The plugin will migrate your database to include the RestockNotification entity, which consists of a variant id of a sold out item and a jsonb list of arrays that wish to be notified about restocks for the item. -You can resolve the Twilio SMS service to dynamically send SMS's via Twilio. -Example: +## API endpoint + +The plugin exposes an endpoint to sign emails up for restock notifications: + +``` +POST /restock-notifications/variants/:variant_id + +Body +{ + "email": "seb@test.com" +} +``` + +The endpoint responds with `200 OK` on succesful signups. If a signup for an already in stock item is attempted the endpoint will have a 400 response code. + + +## Restock events + +The plugin listens for the `product-variant.updated` call and emits a `restock-notification.restocked` event when a variant with restock signups become available. + +The data sent with the `restock-notification.restocked` event is: +``` +variant_id: The id of the variant to listen for restock events for. +emails: An array of emails that are to be notified of restocks. + +e.g. + +{ + "variant_id": "variant_1234567890", + "emails": ["seb@test.com", "oli@test.com"] +} +``` + +*Note: This plugin does not send any communication to the customer, communication logic must be implemented or provided through a communication plugin.* + +You may use `medusa-plugin-sendgrid` to orchestrate transactional emails. + + +## Usage with medusa-plugin-sendgrid + +Install the plugins: +`$ yarn add medusa-plugin-restock-notification medusa-plugin-sendgrid` ```js -const twilioSmsService = scope.resolve("twilioSmsService") -twilioSmsService.sendSms({ to: "+4500112233", body: "Hello world!" }) +// medusa-config.js + +module.exports = { + ..., + plugins: [ + ..., + `medusa-plugin-restock-notification`, + { + resolve: `medusa-plugin-sendgrid`, + options: { + from: SENDGRID_FROM, + api_key: SENDGRID_API_KEY, + medusa_restock_template: `d-13141234123412342314` + } + } + ] +} ``` + +You should set up a dynamic template in SendGrid which will be send for each of the emails in the restock notification. + diff --git a/packages/medusa-plugin-restock-notification/src/api/routes/add-email.js b/packages/medusa-plugin-restock-notification/src/api/routes/add-email.js index 89f0bb35ce..de8800dcf2 100644 --- a/packages/medusa-plugin-restock-notification/src/api/routes/add-email.js +++ b/packages/medusa-plugin-restock-notification/src/api/routes/add-email.js @@ -18,7 +18,7 @@ export default async (req, res) => { "restockNotificationService" ) await restockNotificationService.addEmail(variant_id, value.email) - res.sendStatus(200) + res.sendStatus(201) } catch (err) { res.status(400).json({ message: err.message }) } diff --git a/packages/medusa-plugin-restock-notification/src/models/restock-notification.ts b/packages/medusa-plugin-restock-notification/src/models/restock-notification.ts index 942daf30ec..16a4707063 100644 --- a/packages/medusa-plugin-restock-notification/src/models/restock-notification.ts +++ b/packages/medusa-plugin-restock-notification/src/models/restock-notification.ts @@ -30,3 +30,25 @@ export class RestockNotification { @UpdateDateColumn({ type: "timestamptz" }) updated_at: Date } + +/** + * @schema restock_notification + * title: "Restock Notification" + * description: "Holds a list of emails that wish to be notifed when an item is restocked." + * x-resourceId: restock_notification + * properties: + * variant_id: + * type: string + * description: "The id of the variant that customers have signed up to be notified about," + * emails: + * type: string[] + * description: "The emails of customers who wish to be notified about restocks." + * created_at: + * type: string + * format: date-time + * description: "The date time at which the first restock signup was made." + * updated_at: + * type: string + * format: date-time + * description: "The date time at which the first last signup was made." + */ diff --git a/packages/medusa-plugin-restock-notification/src/services/__tests__/restock-notification.js b/packages/medusa-plugin-restock-notification/src/services/__tests__/restock-notification.js index a980d7490d..5eaa6ed925 100644 --- a/packages/medusa-plugin-restock-notification/src/services/__tests__/restock-notification.js +++ b/packages/medusa-plugin-restock-notification/src/services/__tests__/restock-notification.js @@ -88,10 +88,7 @@ describe("RestockNotificationService", () => { it("successfully adds email to non-existing noti", async () => { jest.clearAllMocks() - const result = await restockNotiService.addEmail( - "variant_test", - "seb@med-test.com" - ) + await restockNotiService.addEmail("variant_test", "seb@med-test.com") expect(RestockNotificationModel.create).toHaveBeenCalledTimes(1) expect(RestockNotificationModel.create).toHaveBeenCalledWith({ @@ -105,10 +102,7 @@ describe("RestockNotificationService", () => { it("successfully adds email to existing noti", async () => { jest.clearAllMocks() - const result = await restockNotiService.addEmail( - "variant_1234", - "seb@med-test.com" - ) + await restockNotiService.addEmail("variant_1234", "seb@med-test.com") expect(RestockNotificationModel.save).toHaveBeenCalledTimes(1) expect(RestockNotificationModel.save).toHaveBeenCalledWith({ diff --git a/packages/medusa-plugin-restock-notification/src/services/restock-notification.js b/packages/medusa-plugin-restock-notification/src/services/restock-notification.js index f4ae4e20f0..12b7c3b56c 100644 --- a/packages/medusa-plugin-restock-notification/src/services/restock-notification.js +++ b/packages/medusa-plugin-restock-notification/src/services/restock-notification.js @@ -1,6 +1,11 @@ import { MedusaError } from "medusa-core-utils" import { BaseService } from "medusa-interfaces" +/** + * Restock notifications can be used to keep track of customers who wish to be + * notified when a certain item is restocked. Restock notifications can only + * apply to sold out items and will be deleted once items are restocked. + */ class RestockNotificationService extends BaseService { constructor( { @@ -41,6 +46,12 @@ class RestockNotificationService extends BaseService { return cloned } + /** + * Retrieves a restock notification by a given variant id. + * @param {string} variantId - the variant id to retrieve restock notification + * for + * @return {Promise} The restock notification + */ async retrieve(variantId) { const restockRepo = this.manager_.getRepository( this.restockNotificationModel_ @@ -48,6 +59,13 @@ class RestockNotificationService extends BaseService { return await restockRepo.findOne({ where: { variant_id: variantId } }) } + /** + * Adds an email to be notified when a certain variant is restocked. Throws if + * the variant is not sold out. + * @param {string} variantId - the variant id to sign up for notifications for + * @param {string} email - the email to signup + * @return {Promise} The resulting restock notification + */ async addEmail(variantId, email) { return this.atomicPhase_(async (manager) => { const restockRepo = manager.getRepository(this.restockNotificationModel_) @@ -81,6 +99,13 @@ class RestockNotificationService extends BaseService { }) } + /** + * Checks if anyone has signed up for restock notifications on a given variant + * and emits a restocked event to the event bus. After successful emission the + * restock notification is deleted. + * @param {string} variantId - the variant id to trigger restock for + * @return {Promise} The resulting restock notification + */ async triggerRestock(variantId) { return this.atomicPhase_(async (manager) => { const restockRepo = manager.getRepository(this.restockNotificationModel_) diff --git a/packages/medusa-plugin-sendgrid/src/services/sendgrid.js b/packages/medusa-plugin-sendgrid/src/services/sendgrid.js index 5f30c1ec1c..c169c0fe2d 100644 --- a/packages/medusa-plugin-sendgrid/src/services/sendgrid.js +++ b/packages/medusa-plugin-sendgrid/src/services/sendgrid.js @@ -121,6 +121,8 @@ class SendGridService extends NotificationService { return this.userPasswordResetData(eventData, attachmentGenerator) case "customer.password_reset": return this.customerPasswordResetData(eventData, attachmentGenerator) + case "restock-notification.restocked": + return await this.restockNotificationData(eventData, attachmentGenerator) default: return {} } @@ -154,6 +156,8 @@ class SendGridService extends NotificationService { return this.options_.user_password_reset_template case "customer.password_reset": return this.options_.customer_password_reset_template + case "restock-notification.restocked": + return this.options_.medusa_restock_template default: return null } @@ -671,6 +675,19 @@ class SendGridService extends NotificationService { } } + async restockNotificationData({ variant_id, emails }) { + const variant = await this.productVariantService_.retrieve(variant_id, { + relations: ["product"] + }) + + return { + product: variant.product, + variant, + variant_id, + emails + } + } + userPasswordResetData(data) { return data } @@ -698,7 +715,9 @@ class SendGridService extends NotificationService { } const normalized = humanizeAmount(amount, currency) - return normalized.toFixed(zeroDecimalCurrencies.includes(currency.toLowerCase()) ? 0 : 2) + return normalized.toFixed( + zeroDecimalCurrencies.includes(currency.toLowerCase()) ? 0 : 2 + ) } normalizeThumbUrl_(url) { diff --git a/packages/medusa-plugin-sendgrid/src/subscribers/order.js b/packages/medusa-plugin-sendgrid/src/subscribers/order.js index 0bf0dbf28f..f9d71d147f 100644 --- a/packages/medusa-plugin-sendgrid/src/subscribers/order.js +++ b/packages/medusa-plugin-sendgrid/src/subscribers/order.js @@ -13,10 +13,6 @@ class OrderSubscriber { this.notificationService_.subscribe("swap.created", "sendgrid") this.notificationService_.subscribe("order.items_returned", "sendgrid") this.notificationService_.subscribe("order.return_requested", "sendgrid") - this.notificationService_.subscribe( - "restock_notification.restocked", - "sendgrid" - ) } } diff --git a/packages/medusa-plugin-sendgrid/src/subscribers/restock.js b/packages/medusa-plugin-sendgrid/src/subscribers/restock.js new file mode 100644 index 0000000000..0048128e04 --- /dev/null +++ b/packages/medusa-plugin-sendgrid/src/subscribers/restock.js @@ -0,0 +1,41 @@ +class RestockNotification { + constructor({ eventBusService, sendgridService }) { + eventBusService.subscribe( + "restock-notification.restocked", + async (eventData) => { + const templateId = await sendgridService.getTemplateId( + "restock-notification.restocked" + ) + + if (!templateId) { + return + } + + const data = await sendgridService.fetchData( + "restock-notification.restocked", + eventData, + null + ) + + if (!data.emails) { + return + } + + return await Promise.all( + data.emails.map(async (e) => { + const sendOptions = { + template_id: templateId, + from: sendgridService.options_.from, + to: e, + dynamic_template_data: data, + } + + return await sendgridService.sendEmail(sendOptions) + }) + ) + } + ) + } +} + +export default RestockNotification