Files
medusa-store/packages/medusa/src/services/notification.ts
Philip Korsholm 5b91a3503a feat(medusa): Add proper pagination (#4517)
* 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>
2023-07-14 16:14:51 +02:00

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