feat(medusa): Migrate NotificationService to TS + add NotificationInterface (#1922)
This commit is contained in:
@@ -7,6 +7,8 @@ import {
|
|||||||
defaultAdminNotificationsFields,
|
defaultAdminNotificationsFields,
|
||||||
defaultAdminNotificationsRelations,
|
defaultAdminNotificationsRelations,
|
||||||
} from "./"
|
} from "./"
|
||||||
|
import { Notification } from "../../../../models"
|
||||||
|
import { FindConfig } from "../../../../types/common"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @oas [get] /notifications
|
* @oas [get] /notifications
|
||||||
@@ -91,20 +93,23 @@ export default async (req, res) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const listConfig = {
|
const listConfig = {
|
||||||
select: includeFields.length
|
select: (includeFields.length
|
||||||
? includeFields
|
? includeFields
|
||||||
: defaultAdminNotificationsFields,
|
: defaultAdminNotificationsFields) as (keyof Notification)[],
|
||||||
relations: expandFields.length
|
relations: expandFields.length
|
||||||
? expandFields
|
? expandFields
|
||||||
: defaultAdminNotificationsRelations,
|
: defaultAdminNotificationsRelations,
|
||||||
skip: offset,
|
skip: offset,
|
||||||
take: limit,
|
take: limit,
|
||||||
order: { created_at: "DESC" },
|
order: { created_at: "DESC" },
|
||||||
}
|
} as FindConfig<Notification>
|
||||||
|
|
||||||
const notifications = await notificationService.list(selector, listConfig)
|
const notifications = await notificationService.list(selector, listConfig)
|
||||||
|
|
||||||
const resultFields = [...listConfig.select, ...listConfig.relations]
|
const resultFields = [
|
||||||
|
...(listConfig.select ?? []),
|
||||||
|
...(listConfig.relations ?? []),
|
||||||
|
]
|
||||||
const data = notifications.map((o) => pick(o, resultFields))
|
const data = notifications.map((o) => pick(o, resultFields))
|
||||||
|
|
||||||
res.json({ notifications: data })
|
res.json({ notifications: data })
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import {
|
|||||||
} from "."
|
} from "."
|
||||||
import { validator } from "../../../../utils/validator"
|
import { validator } from "../../../../utils/validator"
|
||||||
import { NotificationService } from "../../../../services"
|
import { NotificationService } from "../../../../services"
|
||||||
|
import { Notification } from "../../../../models"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @oas [post] /notifications/{id}/resend
|
* @oas [post] /notifications/{id}/resend
|
||||||
@@ -56,7 +57,7 @@ export default async (req, res) => {
|
|||||||
await notificationService.resend(id, config)
|
await notificationService.resend(id, config)
|
||||||
|
|
||||||
const notification = await notificationService.retrieve(id, {
|
const notification = await notificationService.retrieve(id, {
|
||||||
select: defaultAdminNotificationsFields,
|
select: defaultAdminNotificationsFields as (keyof Notification)[],
|
||||||
relations: defaultAdminNotificationsRelations,
|
relations: defaultAdminNotificationsRelations,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -4,5 +4,7 @@ export * from "./tax-service"
|
|||||||
export * from "./transaction-base-service"
|
export * from "./transaction-base-service"
|
||||||
export * from "./batch-job-strategy"
|
export * from "./batch-job-strategy"
|
||||||
export * from "./file-service"
|
export * from "./file-service"
|
||||||
|
export * from "./notification-service"
|
||||||
|
export * from "./price-selection-strategy"
|
||||||
export * from "./models/base-entity"
|
export * from "./models/base-entity"
|
||||||
export * from "./models/soft-deletable-entity"
|
export * from "./models/soft-deletable-entity"
|
||||||
|
|||||||
@@ -0,0 +1,55 @@
|
|||||||
|
import { TransactionBaseService } from "./transaction-base-service"
|
||||||
|
import BaseNotificationService from "medusa-interfaces/dist/notification-service"
|
||||||
|
|
||||||
|
type ReturnedData = {
|
||||||
|
to: string
|
||||||
|
status: string
|
||||||
|
data: Record<string, unknown>
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface INotificationService<T extends TransactionBaseService<never>>
|
||||||
|
extends TransactionBaseService<T> {
|
||||||
|
sendNotification(
|
||||||
|
event: string,
|
||||||
|
data: unknown,
|
||||||
|
attachmentGenerator: unknown
|
||||||
|
): Promise<ReturnedData>
|
||||||
|
|
||||||
|
resendNotification(
|
||||||
|
notification: unknown,
|
||||||
|
config: unknown,
|
||||||
|
attachmentGenerator: unknown
|
||||||
|
): Promise<ReturnedData>
|
||||||
|
}
|
||||||
|
|
||||||
|
export abstract class AbstractNotificationService<
|
||||||
|
T extends TransactionBaseService<never>
|
||||||
|
>
|
||||||
|
extends TransactionBaseService<T>
|
||||||
|
implements INotificationService<T>
|
||||||
|
{
|
||||||
|
static identifier: string
|
||||||
|
|
||||||
|
getIdentifier(): string {
|
||||||
|
return (this.constructor as any).identifier
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract sendNotification(
|
||||||
|
event: string,
|
||||||
|
data: unknown,
|
||||||
|
attachmentGenerator: unknown
|
||||||
|
): Promise<ReturnedData>
|
||||||
|
|
||||||
|
abstract resendNotification(
|
||||||
|
notification: unknown,
|
||||||
|
config: unknown,
|
||||||
|
attachmentGenerator: unknown
|
||||||
|
): Promise<ReturnedData>
|
||||||
|
}
|
||||||
|
|
||||||
|
export const isNotificationService = (obj: unknown): boolean => {
|
||||||
|
return (
|
||||||
|
obj instanceof AbstractNotificationService ||
|
||||||
|
obj instanceof BaseNotificationService
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -5,7 +5,6 @@ import {
|
|||||||
BaseService as LegacyBaseService,
|
BaseService as LegacyBaseService,
|
||||||
PaymentService,
|
PaymentService,
|
||||||
FulfillmentService,
|
FulfillmentService,
|
||||||
NotificationService,
|
|
||||||
FileService,
|
FileService,
|
||||||
OauthService,
|
OauthService,
|
||||||
SearchService,
|
SearchService,
|
||||||
@@ -21,6 +20,9 @@ import {
|
|||||||
isFileService,
|
isFileService,
|
||||||
isTaxCalculationStrategy,
|
isTaxCalculationStrategy,
|
||||||
TransactionBaseService as BaseService,
|
TransactionBaseService as BaseService,
|
||||||
|
isNotificationService,
|
||||||
|
isBatchJobStrategy,
|
||||||
|
isPriceSelectionStrategy,
|
||||||
} from "../interfaces"
|
} from "../interfaces"
|
||||||
import formatRegistrationName from "../utils/format-registration-name"
|
import formatRegistrationName from "../utils/format-registration-name"
|
||||||
import {
|
import {
|
||||||
@@ -30,8 +32,6 @@ import {
|
|||||||
MedusaContainer,
|
MedusaContainer,
|
||||||
} from "../types/global"
|
} from "../types/global"
|
||||||
import { MiddlewareService } from "../services"
|
import { MiddlewareService } from "../services"
|
||||||
import { isBatchJobStrategy } from "../interfaces/batch-job-strategy"
|
|
||||||
import { isPriceSelectionStrategy } from "../interfaces/price-selection-strategy"
|
|
||||||
import logger from "./logger"
|
import logger from "./logger"
|
||||||
|
|
||||||
type Options = {
|
type Options = {
|
||||||
@@ -392,7 +392,7 @@ export async function registerServices(
|
|||||||
).singleton(),
|
).singleton(),
|
||||||
[`fp_${loaded.identifier}`]: aliasTo(name),
|
[`fp_${loaded.identifier}`]: aliasTo(name),
|
||||||
})
|
})
|
||||||
} else if (loaded.prototype instanceof NotificationService) {
|
} else if (isNotificationService(loaded.prototype)) {
|
||||||
container.registerAdd(
|
container.registerAdd(
|
||||||
"notificationProviders",
|
"notificationProviders",
|
||||||
asFunction((cradle) => new loaded(cradle, pluginDetails.options))
|
asFunction((cradle) => new loaded(cradle, pluginDetails.options))
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ export class Notification extends BaseEntity {
|
|||||||
|
|
||||||
@Index()
|
@Index()
|
||||||
@Column({ nullable: true })
|
@Column({ nullable: true })
|
||||||
customer_id: string
|
customer_id: string | null
|
||||||
|
|
||||||
@ManyToOne(() => Customer)
|
@ManyToOne(() => Customer)
|
||||||
@JoinColumn({ name: "customer_id" })
|
@JoinColumn({ name: "customer_id" })
|
||||||
|
|||||||
@@ -1,261 +0,0 @@
|
|||||||
import { MedusaError } from "medusa-core-utils"
|
|
||||||
import { BaseService } from "medusa-interfaces"
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Provides layer to manipulate orchestrate notifications.
|
|
||||||
* @extends BaseService
|
|
||||||
*/
|
|
||||||
class NotificationService extends BaseService {
|
|
||||||
constructor(container) {
|
|
||||||
super()
|
|
||||||
|
|
||||||
const {
|
|
||||||
manager,
|
|
||||||
notificationProviderRepository,
|
|
||||||
notificationRepository,
|
|
||||||
logger,
|
|
||||||
} = container
|
|
||||||
|
|
||||||
this.container_ = container
|
|
||||||
|
|
||||||
/** @private @const {EntityManager} */
|
|
||||||
this.manager_ = manager
|
|
||||||
this.logger_ = logger
|
|
||||||
|
|
||||||
/** @private @const {NotificationRepository} */
|
|
||||||
this.notificationRepository_ = notificationRepository
|
|
||||||
this.notificationProviderRepository_ = notificationProviderRepository
|
|
||||||
|
|
||||||
this.subscribers_ = {}
|
|
||||||
this.attachmentGenerator_ = null
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Registers an attachment generator to the service. The generator can be
|
|
||||||
* used to generate on demand invoices or other documents.
|
|
||||||
* @param {object} service
|
|
||||||
*/
|
|
||||||
registerAttachmentGenerator(service) {
|
|
||||||
this.attachmentGenerator_ = service
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets the service's manager to a given transaction manager.
|
|
||||||
* @param {EntityManager} transactionManager - the manager to use
|
|
||||||
* @return {NotificationService} a cloned notification service
|
|
||||||
*/
|
|
||||||
withTransaction(transactionManager) {
|
|
||||||
if (!transactionManager) {
|
|
||||||
return this
|
|
||||||
}
|
|
||||||
|
|
||||||
const cloned = new NotificationService({
|
|
||||||
manager: transactionManager,
|
|
||||||
notificationProviderRepository: this.notificationProviderRepository_,
|
|
||||||
notificationRepository: this.notificationRepository_,
|
|
||||||
logger: this.logger_,
|
|
||||||
})
|
|
||||||
|
|
||||||
cloned.transactionManager_ = transactionManager
|
|
||||||
|
|
||||||
return cloned
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Takes a list of notification provider ids and persists them in the database.
|
|
||||||
* @param {Array<string>} providers - a list of provider ids
|
|
||||||
*/
|
|
||||||
async registerInstalledProviders(providers) {
|
|
||||||
const { manager, notificationProviderRepository } = this.container_
|
|
||||||
const model = manager.getCustomRepository(notificationProviderRepository)
|
|
||||||
model.update({}, { is_installed: false })
|
|
||||||
for (const p of providers) {
|
|
||||||
const n = model.create({ id: p, is_installed: true })
|
|
||||||
await model.save(n)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Retrieves a list of notifications.
|
|
||||||
* @param {object} selector - the params to select the notifications by.
|
|
||||||
* @param {object} config - the configuration to apply to the query
|
|
||||||
* @return {Array<Notification>} the notifications that satisfy the query.
|
|
||||||
*/
|
|
||||||
async list(
|
|
||||||
selector,
|
|
||||||
config = { skip: 0, take: 50, order: { created_at: "DESC" } }
|
|
||||||
) {
|
|
||||||
const notiRepo = this.manager_.getCustomRepository(
|
|
||||||
this.notificationRepository_
|
|
||||||
)
|
|
||||||
const query = this.buildQuery_(selector, config)
|
|
||||||
return notiRepo.find(query)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Retrieves a notification with a given id
|
|
||||||
* @param {string} id - the id of the notification
|
|
||||||
* @param {object} config - the configuration to apply to the query
|
|
||||||
* @return {Notification} the notification
|
|
||||||
*/
|
|
||||||
async retrieve(id, config = {}) {
|
|
||||||
const notiRepository = this.manager_.getCustomRepository(
|
|
||||||
this.notificationRepository_
|
|
||||||
)
|
|
||||||
|
|
||||||
const validatedId = this.validateId_(id)
|
|
||||||
const query = this.buildQuery_({ id: validatedId }, config)
|
|
||||||
|
|
||||||
const notification = await notiRepository.findOne(query)
|
|
||||||
|
|
||||||
if (!notification) {
|
|
||||||
throw new MedusaError(
|
|
||||||
MedusaError.Types.NOT_FOUND,
|
|
||||||
`Notification with id: ${id} was not found.`
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
return notification
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Subscribes a given provider to an event.
|
|
||||||
* @param {string} eventName - the event to subscribe to
|
|
||||||
* @param {string} providerId - the provider that the event will be sent to
|
|
||||||
*/
|
|
||||||
subscribe(eventName, providerId) {
|
|
||||||
if (typeof providerId !== "string") {
|
|
||||||
throw new MedusaError(
|
|
||||||
MedusaError.Types.NOT_ALLOWED,
|
|
||||||
"providerId must be a string"
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.subscribers_[eventName]) {
|
|
||||||
this.subscribers_[eventName].push(providerId)
|
|
||||||
} else {
|
|
||||||
this.subscribers_[eventName] = [providerId]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Finds a provider with a given id. Will throw a NOT_FOUND error if the
|
|
||||||
* resolution fails.
|
|
||||||
* @param {string} id - the id of the provider
|
|
||||||
* @return {NotificationProvider} the notification provider
|
|
||||||
*/
|
|
||||||
retrieveProvider_(id) {
|
|
||||||
try {
|
|
||||||
return this.container_[`noti_${id}`]
|
|
||||||
} catch (err) {
|
|
||||||
throw new MedusaError(
|
|
||||||
MedusaError.Types.NOT_FOUND,
|
|
||||||
`Could not find a notification provider with id: ${id}.`
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Handles an event by relaying the event data to the subscribing providers.
|
|
||||||
* The result of the notification send will be persisted in the database in
|
|
||||||
* order to allow for resends. Will log any errors that are encountered.
|
|
||||||
* @param {string} eventName - the event to handle
|
|
||||||
* @param {object} data - the data the event was sent with
|
|
||||||
* @return {Promise} - the result of notification subscribed
|
|
||||||
*/
|
|
||||||
handleEvent(eventName, data) {
|
|
||||||
const subs = this.subscribers_[eventName]
|
|
||||||
if (!subs) {
|
|
||||||
return Promise.resolve()
|
|
||||||
}
|
|
||||||
if (data["no_notification"] === true) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
return Promise.all(
|
|
||||||
subs.map(async (providerId) => {
|
|
||||||
return this.send(eventName, data, providerId).catch((err) => {
|
|
||||||
console.log(err)
|
|
||||||
this.logger_.warn(
|
|
||||||
`An error occured while ${providerId} was processing a notification for ${eventName}: ${err.message}`
|
|
||||||
)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sends a notification, by calling the given provider's sendNotification
|
|
||||||
* method. Persists the Notification in the database.
|
|
||||||
* @param {string} event - the name of the event
|
|
||||||
* @param {object} eventData - the data the event was sent with
|
|
||||||
* @param {string} providerId - the provider that should hande the event.
|
|
||||||
* @return {Notification} the created notification
|
|
||||||
*/
|
|
||||||
async send(event, eventData, providerId) {
|
|
||||||
const provider = this.retrieveProvider_(providerId)
|
|
||||||
const result = await provider.sendNotification(
|
|
||||||
event,
|
|
||||||
eventData,
|
|
||||||
this.attachmentGenerator_
|
|
||||||
)
|
|
||||||
|
|
||||||
if (!result) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
const { to, data } = result
|
|
||||||
const notiRepo = this.manager_.getCustomRepository(
|
|
||||||
this.notificationRepository_
|
|
||||||
)
|
|
||||||
|
|
||||||
const [resource_type] = event.split(".")
|
|
||||||
const resource_id = eventData.id
|
|
||||||
const customer_id = eventData.customer_id || null
|
|
||||||
|
|
||||||
const created = notiRepo.create({
|
|
||||||
resource_type,
|
|
||||||
resource_id,
|
|
||||||
customer_id,
|
|
||||||
to,
|
|
||||||
data,
|
|
||||||
event_name: event,
|
|
||||||
provider_id: providerId,
|
|
||||||
})
|
|
||||||
|
|
||||||
return notiRepo.save(created)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Resends a notification by retrieving a prior notification and calling the
|
|
||||||
* underlying provider's resendNotification method.
|
|
||||||
* @param {string} id - the id of the notification
|
|
||||||
* @param {object} config - any configuration that might override the previous
|
|
||||||
* send
|
|
||||||
* @return {Notification} the newly created notification
|
|
||||||
*/
|
|
||||||
async resend(id, config = {}) {
|
|
||||||
const notification = await this.retrieve(id)
|
|
||||||
|
|
||||||
const provider = this.retrieveProvider_(notification.provider_id)
|
|
||||||
const { to, data } = await provider.resendNotification(
|
|
||||||
notification,
|
|
||||||
config,
|
|
||||||
this.attachmentGenerator_
|
|
||||||
)
|
|
||||||
|
|
||||||
const notiRepo = this.manager_.getCustomRepository(
|
|
||||||
this.notificationRepository_
|
|
||||||
)
|
|
||||||
const created = notiRepo.create({
|
|
||||||
...notification,
|
|
||||||
to,
|
|
||||||
data,
|
|
||||||
parent_id: id,
|
|
||||||
})
|
|
||||||
|
|
||||||
return notiRepo.save(created)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default NotificationService
|
|
||||||
@@ -0,0 +1,282 @@
|
|||||||
|
import { MedusaError } from "medusa-core-utils"
|
||||||
|
import {
|
||||||
|
AbstractNotificationService,
|
||||||
|
TransactionBaseService,
|
||||||
|
} from "../interfaces"
|
||||||
|
import { EntityManager } from "typeorm"
|
||||||
|
import { Logger } from "../types/global"
|
||||||
|
import { NotificationRepository } from "../repositories/notification"
|
||||||
|
import { NotificationProviderRepository } from "../repositories/notification-provider"
|
||||||
|
import { FindConfig, Selector } from "../types/common"
|
||||||
|
import { buildQuery } from "../utils"
|
||||||
|
import { Notification } from "../models"
|
||||||
|
|
||||||
|
type InjectedDependencies = {
|
||||||
|
manager: EntityManager
|
||||||
|
logger: Logger
|
||||||
|
notificationRepository: typeof NotificationRepository
|
||||||
|
notificationProviderRepository: typeof NotificationProviderRepository
|
||||||
|
}
|
||||||
|
type NotificationProviderKey = `noti_${string}`
|
||||||
|
|
||||||
|
class NotificationService extends TransactionBaseService<NotificationService> {
|
||||||
|
protected manager_: EntityManager
|
||||||
|
protected transactionManager_: EntityManager | undefined
|
||||||
|
|
||||||
|
protected subscribers_ = {}
|
||||||
|
protected attachmentGenerator_: unknown = null
|
||||||
|
protected readonly container_: InjectedDependencies & {
|
||||||
|
[key in `${NotificationProviderKey}`]: AbstractNotificationService<never>
|
||||||
|
}
|
||||||
|
protected readonly logger_: Logger
|
||||||
|
protected readonly notificationRepository_: typeof NotificationRepository
|
||||||
|
protected readonly notificationProviderRepository_: typeof NotificationProviderRepository
|
||||||
|
|
||||||
|
constructor(container: InjectedDependencies) {
|
||||||
|
super(container)
|
||||||
|
|
||||||
|
const {
|
||||||
|
manager,
|
||||||
|
notificationProviderRepository,
|
||||||
|
notificationRepository,
|
||||||
|
logger,
|
||||||
|
} = container
|
||||||
|
|
||||||
|
this.container_ = container
|
||||||
|
|
||||||
|
/** @private @const {EntityManager} */
|
||||||
|
this.manager_ = manager
|
||||||
|
this.logger_ = logger
|
||||||
|
|
||||||
|
/** @private @const {NotificationRepository} */
|
||||||
|
this.notificationRepository_ = notificationRepository
|
||||||
|
this.notificationProviderRepository_ = notificationProviderRepository
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Registers an attachment generator to the service. The generator can be
|
||||||
|
* used to generate on demand invoices or other documents.
|
||||||
|
* @param service the service to assign to the attachmentGenerator
|
||||||
|
*/
|
||||||
|
registerAttachmentGenerator(service: unknown): void {
|
||||||
|
this.attachmentGenerator_ = service
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Takes a list of notification provider ids and persists them in the database.
|
||||||
|
* @param providerIds - a list of provider ids
|
||||||
|
*/
|
||||||
|
async registerInstalledProviders(providerIds: string[]): Promise<void> {
|
||||||
|
const { manager, notificationProviderRepository } = this.container_
|
||||||
|
const model = manager.getCustomRepository(notificationProviderRepository)
|
||||||
|
await model.update({}, { is_installed: false })
|
||||||
|
for (const id of providerIds) {
|
||||||
|
const n = model.create({ id, is_installed: true })
|
||||||
|
await model.save(n)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves a list of notifications.
|
||||||
|
* @param selector - the params to select the notifications by.
|
||||||
|
* @param config - the configuration to apply to the query
|
||||||
|
* @return the notifications that satisfy the query.
|
||||||
|
*/
|
||||||
|
async list(
|
||||||
|
selector: Selector<Notification>,
|
||||||
|
config: FindConfig<Notification> = {
|
||||||
|
skip: 0,
|
||||||
|
take: 50,
|
||||||
|
order: { created_at: "DESC" },
|
||||||
|
}
|
||||||
|
): Promise<Notification[]> {
|
||||||
|
const notiRepo = this.manager_.getCustomRepository(
|
||||||
|
this.notificationRepository_
|
||||||
|
)
|
||||||
|
const query = buildQuery(selector, config)
|
||||||
|
return await notiRepo.find(query)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves a notification with a given id
|
||||||
|
* @param id - the id of the notification
|
||||||
|
* @param config - the configuration to apply to the query
|
||||||
|
* @return the notification
|
||||||
|
*/
|
||||||
|
async retrieve(
|
||||||
|
id: string,
|
||||||
|
config: FindConfig<Notification> = {}
|
||||||
|
): Promise<Notification | never> {
|
||||||
|
const notiRepository = this.manager_.getCustomRepository(
|
||||||
|
this.notificationRepository_
|
||||||
|
)
|
||||||
|
|
||||||
|
const query = buildQuery({ id }, config)
|
||||||
|
|
||||||
|
const notification = await notiRepository.findOne(query)
|
||||||
|
|
||||||
|
if (!notification) {
|
||||||
|
throw new MedusaError(
|
||||||
|
MedusaError.Types.NOT_FOUND,
|
||||||
|
`Notification with id: ${id} was not found.`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return notification
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Subscribes a given provider to an event.
|
||||||
|
* @param eventName - the event to subscribe to
|
||||||
|
* @param providerId - the provider that the event will be sent to
|
||||||
|
*/
|
||||||
|
subscribe(eventName: string, providerId: string): void {
|
||||||
|
if (typeof providerId !== "string") {
|
||||||
|
throw new MedusaError(
|
||||||
|
MedusaError.Types.NOT_ALLOWED,
|
||||||
|
"providerId must be a string"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.subscribers_[eventName]) {
|
||||||
|
this.subscribers_[eventName].push(providerId)
|
||||||
|
} else {
|
||||||
|
this.subscribers_[eventName] = [providerId]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Finds a provider with a given id. Will throw a NOT_FOUND error if the
|
||||||
|
* resolution fails.
|
||||||
|
* @param id - the id of the provider
|
||||||
|
* @return the notification provider
|
||||||
|
*/
|
||||||
|
protected retrieveProvider_(id: string): AbstractNotificationService<never> {
|
||||||
|
try {
|
||||||
|
return this.container_[`noti_${id}`]
|
||||||
|
} catch (err) {
|
||||||
|
throw new MedusaError(
|
||||||
|
MedusaError.Types.NOT_FOUND,
|
||||||
|
`Could not find a notification provider with id: ${id}.`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles an event by relaying the event data to the subscribing providers.
|
||||||
|
* The result of the notification send will be persisted in the database in
|
||||||
|
* order to allow for resends. Will log any errors that are encountered.
|
||||||
|
* @param eventName - the event to handle
|
||||||
|
* @param data - the data the event was sent with
|
||||||
|
* @return the result of notification subscribed
|
||||||
|
*/
|
||||||
|
handleEvent(
|
||||||
|
eventName: string,
|
||||||
|
data: Record<string, unknown>
|
||||||
|
): Promise<void | undefined | Notification[]> {
|
||||||
|
const subs = this.subscribers_[eventName]
|
||||||
|
if (!subs) {
|
||||||
|
return Promise.resolve()
|
||||||
|
}
|
||||||
|
if (data["no_notification"] === true) {
|
||||||
|
return Promise.resolve()
|
||||||
|
}
|
||||||
|
|
||||||
|
return Promise.all(
|
||||||
|
subs.map(async (providerId) => {
|
||||||
|
return this.send(eventName, data, providerId).catch((err) => {
|
||||||
|
console.log(err)
|
||||||
|
this.logger_.warn(
|
||||||
|
`An error occured while ${providerId} was processing a notification for ${eventName}: ${err.message}`
|
||||||
|
)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sends a notification, by calling the given provider's sendNotification
|
||||||
|
* method. Persists the Notification in the database.
|
||||||
|
* @param event - the name of the event
|
||||||
|
* @param eventData - the data the event was sent with
|
||||||
|
* @param providerId - the provider that should hande the event.
|
||||||
|
* @return the created notification
|
||||||
|
*/
|
||||||
|
async send(
|
||||||
|
event: string,
|
||||||
|
eventData: Record<string, unknown>,
|
||||||
|
providerId: string
|
||||||
|
): Promise<Notification | undefined> {
|
||||||
|
return await this.atomicPhase_(async (transactionManager) => {
|
||||||
|
const provider = this.retrieveProvider_(providerId)
|
||||||
|
const result = await provider.sendNotification(
|
||||||
|
event,
|
||||||
|
eventData,
|
||||||
|
this.attachmentGenerator_
|
||||||
|
)
|
||||||
|
|
||||||
|
if (!result) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const { to, data } = result
|
||||||
|
const notiRepo = transactionManager.getCustomRepository(
|
||||||
|
this.notificationRepository_
|
||||||
|
)
|
||||||
|
|
||||||
|
const [resource_type] = event.split(".") as string[]
|
||||||
|
const resource_id = eventData.id as string
|
||||||
|
const customer_id = (eventData.customer_id as string) || null
|
||||||
|
|
||||||
|
const created = notiRepo.create({
|
||||||
|
resource_type,
|
||||||
|
resource_id,
|
||||||
|
customer_id,
|
||||||
|
to,
|
||||||
|
data,
|
||||||
|
event_name: event,
|
||||||
|
provider_id: providerId,
|
||||||
|
})
|
||||||
|
|
||||||
|
return await notiRepo.save(created)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resends a notification by retrieving a prior notification and calling the
|
||||||
|
* underlying provider's resendNotification method.
|
||||||
|
* @param {string} id - the id of the notification
|
||||||
|
* @param {object} config - any configuration that might override the previous
|
||||||
|
* send
|
||||||
|
* @return {Notification} the newly created notification
|
||||||
|
*/
|
||||||
|
async resend(
|
||||||
|
id: string,
|
||||||
|
config: FindConfig<Notification> = {}
|
||||||
|
): Promise<Notification> {
|
||||||
|
return await this.atomicPhase_(async (transactionManager) => {
|
||||||
|
const notification = await this.retrieve(id)
|
||||||
|
|
||||||
|
const provider = this.retrieveProvider_(notification.provider_id)
|
||||||
|
const { to, data } = await provider.resendNotification(
|
||||||
|
notification,
|
||||||
|
config,
|
||||||
|
this.attachmentGenerator_
|
||||||
|
)
|
||||||
|
|
||||||
|
const notiRepo = transactionManager.getCustomRepository(
|
||||||
|
this.notificationRepository_
|
||||||
|
)
|
||||||
|
const created = notiRepo.create({
|
||||||
|
...notification,
|
||||||
|
to,
|
||||||
|
data,
|
||||||
|
parent_id: id,
|
||||||
|
})
|
||||||
|
|
||||||
|
return notiRepo.save(created)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default NotificationService
|
||||||
Reference in New Issue
Block a user