* update method for listing regions * add changeset * fix unit tests * listAndCount swaps * add count calculation to list-returns * swap integration test * notes pagination * pagination props for notifications * listAndCount store regions * fix nit * fix note unit test * update list-regions store unit test * cleanup integration test * rename introduced tests --------- Co-authored-by: Oliver Windall Juhl <59018053+olivermrbl@users.noreply.github.com>
296 lines
8.9 KiB
TypeScript
296 lines
8.9 KiB
TypeScript
import {
|
|
AbstractNotificationService,
|
|
TransactionBaseService,
|
|
} from "../interfaces"
|
|
import { FindConfig, Selector } from "../types/common"
|
|
|
|
import { EntityManager } from "typeorm"
|
|
import { Logger } from "../types/global"
|
|
import { MedusaError } from "medusa-core-utils"
|
|
import { Notification } from "../models"
|
|
import { NotificationProviderRepository } from "../repositories/notification-provider"
|
|
import { NotificationRepository } from "../repositories/notification"
|
|
import { buildQuery } from "../utils"
|
|
|
|
type InjectedDependencies = {
|
|
manager: EntityManager
|
|
logger: Logger
|
|
notificationRepository: typeof NotificationRepository
|
|
notificationProviderRepository: typeof NotificationProviderRepository
|
|
}
|
|
type NotificationProviderKey = `noti_${string}`
|
|
|
|
class NotificationService extends TransactionBaseService {
|
|
protected subscribers_ = {}
|
|
protected attachmentGenerator_: unknown = null
|
|
protected readonly container_: InjectedDependencies & {
|
|
[key in `${NotificationProviderKey}`]: AbstractNotificationService
|
|
}
|
|
protected readonly logger_: Logger
|
|
protected readonly notificationRepository_: typeof NotificationRepository
|
|
// eslint-disable-next-line max-len
|
|
protected readonly notificationProviderRepository_: typeof NotificationProviderRepository
|
|
|
|
constructor(container: InjectedDependencies) {
|
|
super(container)
|
|
|
|
const { notificationProviderRepository, notificationRepository, logger } =
|
|
container
|
|
|
|
this.container_ = container
|
|
|
|
this.logger_ = logger
|
|
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 { notificationProviderRepository } = this.container_
|
|
const model = this.activeManager_.withRepository(
|
|
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 [notifications] = await this.listAndCount(selector, config)
|
|
|
|
return notifications
|
|
}
|
|
|
|
/**
|
|
* Retrieves a list of notifications and total count.
|
|
* @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 as well as the count.
|
|
*/
|
|
async listAndCount(
|
|
selector: Selector<Notification>,
|
|
config: FindConfig<Notification> = {
|
|
skip: 0,
|
|
take: 50,
|
|
order: { created_at: "DESC" },
|
|
}
|
|
): Promise<[Notification[], number]> {
|
|
const notiRepo = this.activeManager_.withRepository(
|
|
this.notificationRepository_
|
|
)
|
|
const query = buildQuery(selector, config)
|
|
return await notiRepo.findAndCount(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.activeManager_.withRepository(
|
|
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 {
|
|
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
|
|
*/
|
|
async 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.withRepository(
|
|
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.withRepository(
|
|
this.notificationRepository_
|
|
)
|
|
const resendNoti: Record<string, unknown> = { ...notification, id: null }
|
|
const created = notiRepo.create({
|
|
...resendNoti,
|
|
to,
|
|
data,
|
|
parent_id: id,
|
|
})
|
|
|
|
return notiRepo.save(created)
|
|
})
|
|
}
|
|
}
|
|
|
|
export default NotificationService
|