feat: Add the basic implementation of notification module (#7282)

* feat: Add the basic implementation of notification module

* fix: Minor fixes and introduction of idempotency key

* fix: Changes based on PR review
This commit is contained in:
Stevche Radevski
2024-05-10 11:22:03 +02:00
committed by GitHub
parent 6ec5ded6c8
commit 144e09e852
43 changed files with 1666 additions and 1 deletions
@@ -36,6 +36,7 @@ export enum Modules {
STORE = "store",
CURRENCY = "currency",
FILE = "file",
NOTIFICATION = "notification",
}
export enum ModuleRegistrationName {
@@ -61,6 +62,7 @@ export enum ModuleRegistrationName {
STORE = "storeModuleService",
CURRENCY = "currencyModuleService",
FILE = "fileModuleService",
NOTIFICATION = "notificationModuleService",
}
export const MODULE_PACKAGE_NAMES = {
@@ -87,6 +89,7 @@ export const MODULE_PACKAGE_NAMES = {
[Modules.STORE]: "@medusajs/store",
[Modules.CURRENCY]: "@medusajs/currency",
[Modules.FILE]: "@medusajs/file",
[Modules.NOTIFICATION]: "@medusajs/notification",
}
export const ModulesDefinition: { [key: string | Modules]: ModuleDefinition } =
@@ -378,6 +381,19 @@ export const ModulesDefinition: { [key: string | Modules]: ModuleDefinition } =
resources: MODULE_RESOURCE_TYPE.SHARED,
},
},
[Modules.NOTIFICATION]: {
key: Modules.NOTIFICATION,
registrationName: ModuleRegistrationName.NOTIFICATION,
defaultPackage: false,
label: upperCaseFirst(ModuleRegistrationName.NOTIFICATION),
isRequired: false,
isQueryable: true,
dependencies: ["logger"],
defaultModuleDeclaration: {
scope: MODULE_SCOPE.INTERNAL,
resources: MODULE_RESOURCE_TYPE.SHARED,
},
},
}
export const MODULE_DEFINITIONS: ModuleDefinition[] =
+1
View File
@@ -27,3 +27,4 @@ export * as StoreTypes from "./store"
export * as CurrencyTypes from "./currency"
export * as HttpTypes from "./http"
export * as FileTypes from "./file"
export * as NotificationTypes from "./notification"
+1
View File
@@ -38,3 +38,4 @@ export * from "./transaction-base"
export * from "./user"
export * from "./workflow"
export * from "./workflows"
export * from "./notification"
@@ -0,0 +1,138 @@
import { BaseFilterable } from "../dal"
import { OperatorMap } from "../dal/utils"
/**
* @interface
*
* A notification's data.
*/
export interface NotificationDTO {
/**
* The ID of the notification.
*/
id: string
/**
* The recipient of the notification. It can be email, phone number, or username, depending on the channel.
*/
to: string
/**
* The channel through which the notification is sent, such as 'email' or 'sms'
*/
channel: string
/**
* The template name in the provider's system.
*/
template: string
/**
* The data that gets passed over to the provider for rendering the notification.
*/
data: Record<string, unknown> | null
/**
* The event name, the workflow, or anything else that can help to identify what triggered the notification.
*/
trigger_type?: string | null
/**
* The ID of the resource this notification is for, if applicable. Useful for displaying relevant information in the UI
*/
resource_id?: string | null
/**
* The type of the resource this notification is for, if applicable, eg. "order"
*/
resource_type?: string | null
/**
* The ID of the customer this notification is for, if applicable.
*/
receiver_id?: string | null
/**
* The original notification, in case this is a retried notification.
*/
original_notification_id?: string | null
/**
* The id of the notification in the external system, if applicable
*/
external_id?: string | null
/**
* The ID of the notification provider.
*/
provider_id: string
/**
* Information about the notification provider
*/
provider: NotificationProviderDTO
/**
* The date and time the notification was created.
*/
created_at: Date
}
/**
* @interface
*
* Information about the notification provider
*/
export interface NotificationProviderDTO {
/**
* The ID of the notification provider.
*/
id: string
/**
* The handle of the notification provider.
*/
handle: string
/**
* A user-friendly name of the notification provider.
*/
name: string
/**
* The supported channels by the notification provider.
*/
channels: string[]
}
/**
* @interface
*
* The filters to apply on retrieved notifications.
*
* @prop q - Search through the notifications' attributes, such as trigger types and recipients, using this search term.
*/
export interface FilterableNotificationProps
extends BaseFilterable<FilterableNotificationProps> {
/**
* Search through the notifications' attributes, such as trigger types and recipients, using this search term.
*/
q?: string
/**
* Filter based on the recipient of the notification.
*/
to?: string | string[] | OperatorMap<string | string[]>
/**
* Filter based on the channel through which the notification is sent, such as 'email' or 'sms'
*/
channel?: string | string[] | OperatorMap<string | string[]>
/**
* Filter based on the template name.
*/
template?: string | string[] | OperatorMap<string | string[]>
/**
* Filter based on the trigger type.
*/
trigger_type?: string | string[] | OperatorMap<string | string[]>
/**
* Filter based on the resource that was the trigger for the notification.
*/
resource_id?: string | string[] | OperatorMap<string | string[]>
/**
* T* Filter based on the resource type that was the trigger for the notification.
*/
resource_type?: string | string[] | OperatorMap<string | string[]>
/**
* Filter based on the customer ID.
*/
receiver_id?: string | string[] | OperatorMap<string | string[]>
/**
* Filters a notification based on when it was sent and created in the database
*/
created_at?: OperatorMap<string>
}
@@ -0,0 +1,5 @@
export * from "./common"
export * from "./providers"
export * from "./mutations"
export * from "./service"
export * from "./provider"
@@ -0,0 +1,48 @@
/**
* @interface
*
* A notification to send and have created in the DB
*
*/
export interface CreateNotificationDTO {
/**
* The recipient of the notification. It can be email, phone number, or username, depending on the channel.
*/
to: string
/**
* The channel through which the notification is sent, such as 'email' or 'sms'
*/
channel: string
/**
* The template name in the provider's system.
*/
template: string
/**
* The data that gets passed over to the provider for rendering the notification.
*/
data?: Record<string, unknown> | null
/**
* The event name, the workflow, or anything else that can help to identify what triggered the notification.
*/
trigger_type?: string | null
/**
* The ID of the resource this notification is for, if applicable. Useful for displaying relevant information in the UI
*/
resource_id?: string | null
/**
* The type of the resource this notification is for, if applicable, eg. "order"
*/
resource_type?: string | null
/**
* The ID of the customer this notification is for, if applicable.
*/
receiver_id?: string | null
/**
* The original notification, in case this is a retried notification.
*/
original_notification_id?: string | null
/**
* An idempotency key that ensures the same notification is not sent multiple times.
*/
idempotency_key?: string | null
}
@@ -0,0 +1,54 @@
/**
* @interface
*
* The details of the notification to send.
*/
export type ProviderSendNotificationDTO = {
/**
* The recipient of the notification. It can be email, phone number, or username, depending on the channel.
*/
to: string
/**
* The channel through which the notification is sent, such as 'email' or 'sms'
*/
channel: string
/**
* The template name in the provider's system.
*/
template: string
/**
* The data that gets passed over to the provider for rendering the notification.
*/
data?: Record<string, unknown> | null
}
/**
* @interface
*
* The result of sending the notification
*/
export type ProviderSendNotificationResultsDTO = {
/**
* The ID of the notification in the external system, if provided in the response
*/
id?: string
}
/**
* ## Overview
*
* Notification provider interface for the notification module.
*
*/
export interface INotificationProvider {
/**
* This method is used to send a notification.
*
* @param {ProviderSendNotificationDTO} notification - All information needed to send a notification.
* @returns {Promise<ProviderSendNotificationResultsDTO>} The result of sending the notification.
*
*/
send(
notification: ProviderSendNotificationDTO
): Promise<ProviderSendNotificationResultsDTO>
}
@@ -0,0 +1 @@
export * from "./local"
@@ -0,0 +1 @@
export interface LocalNotificationServiceOptions {}
@@ -0,0 +1,199 @@
import { FindConfig } from "../common"
import { IModuleService } from "../modules-sdk"
import { Context } from "../shared-context"
import { FilterableNotificationProps, NotificationDTO } from "./common"
import { CreateNotificationDTO } from "./mutations"
/**
* The main service interface for the Notification Module.
*/
export interface INotificationModuleService extends IModuleService {
/**
* This method is used to send multiple notifications, and store the requests in the DB.
*
* @param {CreateNotificationDTO[]} data - The notifications to be sent.
* @param {Context} sharedContext - A context used to share resources, such as transaction manager, between the application and the module.
* @returns {Promise<NotificationDTO[]>} The list of sent notifications.
*
* @example
* const notifications = await notificationModuleService.create([
* {
* to: "john@doe.me",
* template: "order-confirmation",
* channel: "email",
* },
* {
* to: "+38975123456",
* template: "order-confirmation",
* channel: "sms",
* },
* ])
*/
create(
data: CreateNotificationDTO[],
sharedContext?: Context
): Promise<NotificationDTO[]>
/**
* This method is used to send a notification, and store the request in the DB.
*
* @param {CreateNotificationDTO} data - The notification to be sent.
* @param {Context} sharedContext - A context used to share resources, such as transaction manager, between the application and the module.
* @returns {Promise<NotificationDTO>} The sent notification.
*
* @example
* const notification = await notificationModuleService.create({
* to: "john@doe.me",
* template: "order-confirmation",
* channel: "email",
* })
*/
create(
data: CreateNotificationDTO,
sharedContext?: Context
): Promise<NotificationDTO>
/**
* This method is used to retrieve a notification by its ID
*
* @param {string} notificationId - The ID of the notification to retrieve.
* @param {FindConfig<NotificationDTO>} config -
* The configurations determining how the notification is retrieved. Its properties, such as `select` or `relations`, accept the
* attributes or relations associated with a notification.
* @param {Context} sharedContext - A context used to share resources, such as transaction manager, between the application and the module.
* @returns {Promise<NotificationDTO>} The retrieved notification.
*
* @example
* A simple example that retrieves a notification by its ID:
*
* ```ts
* const notification =
* await notificationModuleService.retrieve("noti_123")
* ```
*
* To specify relations that should be retrieved:
*
* ```ts
* const notification = await notificationModuleService.retrieve(
* "noti_123",
* {
* relations: ["provider"],
* }
* )
* ```
*/
retrieve(
notificationId: string,
config?: FindConfig<NotificationDTO>,
sharedContext?: Context
): Promise<NotificationDTO>
/**
* This method is used to retrieve a paginated list of notifications based on optional filters and configuration.
*
* @param {FilterableNotificationProps} filters - The filters to apply on the retrieved notifications.
* @param {FindConfig<NotificationDTO>} config -
* The configurations determining how the notifications are retrieved. Its properties, such as `select` or `relations`, accept the
* attributes or relations associated with a notification.
* @param {Context} sharedContext - A context used to share resources, such as transaction manager, between the application and the module.
* @returns {Promise<NotificationDTO[]>} The list of notifications.
*
* @example
* To retrieve a list of notifications using their IDs:
*
* ```ts
* const notifications = await notificationModuleService.list({
* id: ["noti_123", "noti_321"],
* })
* ```
*
* To specify relations that should be retrieved within the notifications:
*
* ```ts
* const notifications = await notificationModuleService.list(
* {
* id: ["noti_123", "noti_321"],
* },
* {
* relations: ["provider"],
* }
* )
* ```
*
* By default, only the first `15` records are retrieved. You can control pagination by specifying the `skip` and `take` properties of the `config` parameter:
*
* ```ts
* const notifications = await notificationModuleService.list(
* {
* id: ["noti_123", "noti_321"],
* },
* {
* relations: ["provider"],
* take: 20,
* skip: 2,
* }
* )
* ```
*/
list(
filters?: FilterableNotificationProps,
config?: FindConfig<NotificationDTO>,
sharedContext?: Context
): Promise<NotificationDTO[]>
/**
* This method is used to retrieve a paginated list of notifications along with the total count of available notifications satisfying the provided filters.
*
* @param {FilterableNotificationProps} filters - The filters to apply on the retrieved notifications.
* @param {FindConfig<NotificationDTO>} config -
* The configurations determining how the notifications are retrieved. Its properties, such as `select` or `relations`, accept the
* attributes or relations associated with a notification.
* @param {Context} sharedContext - A context used to share resources, such as transaction manager, between the application and the module.
* @returns {Promise<NotificationDTO[]>} The list of notifications along with the total count.
*
* @example
* To retrieve a list of notifications using their IDs:
*
* ```ts
* const [notifications, count] =
* await notificationModuleService.listAndCount({
* id: ["noti_123", "noti_321"],
* })
* ```
*
* To specify relations that should be retrieved within the notifications:
*
* ```ts
* const [notifications, count] =
* await notificationModuleService.listAndCount(
* {
* id: ["noti_123", "noti_321"],
* },
* {
* relations: ["provider"],
* }
* )
* ```
*
* By default, only the first `15` records are retrieved. You can control pagination by specifying the `skip` and `take` properties of the `config` parameter:
*
* ```ts
* const [notifications, count] =
* await notificationModuleService.listAndCount(
* {
* id: ["noti_123", "noti_321"],
* },
* {
* relations: ["provider"],
* take: 20,
* skip: 2,
* }
* )
* ```
*/
listAndCount(
filters?: FilterableNotificationProps,
config?: FindConfig<NotificationDTO>,
sharedContext?: Context
): Promise<[NotificationDTO[], number]>
}
+1 -1
View File
@@ -79,7 +79,7 @@ export interface IProductModuleService extends IModuleService {
): Promise<ProductDTO>
/**
* This method is used to retrieve a paginated list of price sets based on optional filters and configuration.
* This method is used to retrieve a paginated list of products based on optional filters and configuration.
*
* @param {FilterableProductProps} filters - The filters to apply on the retrieved products.
* @param {FindConfig<ProductDTO>} config -
+1
View File
@@ -24,5 +24,6 @@ export * from "./user"
export * from "./api-key"
export * from "./link"
export * from "./file"
export * from "./notification"
export const MedusaModuleType = Symbol.for("MedusaModule")
@@ -22,4 +22,5 @@ export enum Modules {
STORE = "store",
CURRENCY = "currency",
FILE = "file",
NOTIFICATION = "notification",
}
@@ -0,0 +1,15 @@
import { NotificationTypes, INotificationProvider } from "@medusajs/types"
export class AbstractNotificationProviderService
implements INotificationProvider
{
async send(
notification: NotificationTypes.ProviderSendNotificationDTO
): Promise<NotificationTypes.ProviderSendNotificationResultsDTO> {
throw Error(
`send is not implemented in ${
Object.getPrototypeOf(this).constructor.name
}`
)
}
}
@@ -0,0 +1 @@
export * from "./abstract-notification-provider"