diff --git a/packages/api-workflows/src/domain/notification/abstractions.ts b/packages/api-workflows/src/domain/notification/abstractions.ts new file mode 100644 index 00000000000..90ff93e9471 --- /dev/null +++ b/packages/api-workflows/src/domain/notification/abstractions.ts @@ -0,0 +1,181 @@ +import { createAbstraction } from "@webiny/feature/api"; +import type { NonEmptyArray } from "@webiny/api/types.js"; + +/** + * Notification Message + */ +export interface INotificationMessageTitleParams { + id: string; + entryTitle: string; +} + +export interface INotificationMessageBodyParams { + id: string; + entryTitle: string; +} + +export interface INotificationMessageUrlParams { + id: string; + entryTitle: string; +} + +export interface INotificationMessage { + getTitle(params: INotificationMessageTitleParams): string; + getBody(params: INotificationMessageBodyParams): string; + getUrl(params: INotificationMessageUrlParams): string; +} + +export const NotificationReviewRequestMessage = createAbstraction( + "WorkflowNotificationReviewRequestMessage" +); + +export namespace NotificationReviewRequestMessage { + export type Interface = INotificationMessage; +} + +export const NotificationReviewCancelMessage = createAbstraction( + "WorkflowNotificationReviewCancelMessage" +); + +export namespace NotificationReviewCancelMessage { + export type Interface = INotificationMessage; +} + +export const NotificationReviewStepStartMessage = createAbstraction( + "WorkflowNotificationReviewStepStartMessage" +); + +export namespace NotificationReviewStepStartMessage { + export type Interface = INotificationMessage; +} + +export const NotificationReviewStepApproveMessage = createAbstraction( + "WorkflowNotificationReviewStepApproveMessage" +); + +export namespace NotificationReviewStepApproveMessage { + export type Interface = INotificationMessage; +} + +export const NotificationReviewRejectMessage = createAbstraction( + "WorkflowNotificationReviewRejectMessage" +); + +export namespace NotificationReviewRejectMessage { + export type Interface = INotificationMessage; +} + +export const NotificationReviewApproveMessage = createAbstraction( + "WorkflowNotificationReviewApproveMessage" +); + +export namespace NotificationReviewApproveMessage { + export type Interface = INotificationMessage; +} + +export type INotificationMessages = + | NotificationReviewRequestMessage.Interface + | NotificationReviewCancelMessage.Interface + | NotificationReviewStepStartMessage.Interface + | NotificationReviewStepApproveMessage.Interface + | NotificationReviewRejectMessage.Interface + | NotificationReviewApproveMessage.Interface; + +export interface INotificationTypeMessages { + /** + * User requests a review for a content. + */ + reviewRequest: NotificationReviewRequestMessage.Interface; + /** + * User cancels a review for a content. + */ + reviewCancel: NotificationReviewCancelMessage.Interface; + /** + * Reviewer starts a review step. + */ + reviewStepStart: NotificationReviewStepStartMessage.Interface; + /** + * Reviewer approves a review step - there are more steps after this one. + */ + reviewStepApprove: NotificationReviewStepApproveMessage.Interface; + /** + * Reviewer rejects a review - no step specific reject because as soon as step is rejected, whole review is rejected. + */ + reviewReject: NotificationReviewRejectMessage.Interface; + /** + * Reviewer approves a final review step. + */ + reviewApprove: NotificationReviewApproveMessage.Interface; +} + +/** + * Notification Type + */ +export interface INotificationType { + id: string; + title: string; +} + +export const NotificationType = createAbstraction("WorkflowNotificationType"); + +export namespace NotificationType { + export type Interface = INotificationType; +} + +/** + * Notification Message Body Converter + */ +export interface INotificationMessageBodyConverter { + convert(body: string): string; +} + +export const NotificationMessageBodyConverter = + createAbstraction("NotificationMessageBodyConverter"); + +export namespace NotificationMessageBodyConverter { + export type Interface = INotificationMessageBodyConverter; +} + +/** + * Notification Adapter + */ +export interface INotificationAdapterUser { + id: string; + email: string; + displayName: string; +} + +export interface INotificationAdapterMessage { + type: keyof INotificationTypeMessages; + title: string; + url: string; + body: string; +} + +export interface INotificationAdapterSendParams { + users: NonEmptyArray; + message: INotificationAdapterMessage; +} + +export interface INotificationAdapter { + id: string; + title: string; + getMessageBodyConverter(): NotificationMessageBodyConverter.Interface | null; + send(params: INotificationAdapterSendParams): Promise; +} + +export const NotificationAdapter = createAbstraction( + "WorkflowNotificationAdapter" +); + +export namespace NotificationAdapter { + export type Interface = INotificationAdapter; + export type Params = INotificationAdapterSendParams; + export type User = INotificationAdapterUser; +} + +export namespace Notification { + export type Type = INotificationType; + export type Adapter = INotificationAdapter; + export type MessageBodyConverter = INotificationMessageBodyConverter; +} diff --git a/packages/api-workflows/src/domain/notifications/errors.ts b/packages/api-workflows/src/domain/notification/errors.ts similarity index 100% rename from packages/api-workflows/src/domain/notifications/errors.ts rename to packages/api-workflows/src/domain/notification/errors.ts diff --git a/packages/api-workflows/src/domain/notification/messages/NotificationMessageOnRequestReview.ts b/packages/api-workflows/src/domain/notification/messages/NotificationMessageOnRequestReview.ts new file mode 100644 index 00000000000..f41ca88f1d7 --- /dev/null +++ b/packages/api-workflows/src/domain/notification/messages/NotificationMessageOnRequestReview.ts @@ -0,0 +1,25 @@ +import { + type INotificationMessageBodyParams, + type INotificationMessageTitleParams, + type INotificationMessageUrlParams, + NotificationReviewRequestMessage as NotificationMessage +} from "../abstractions.js"; + +class NotificationReviewRequestMessageImpl implements NotificationMessage.Interface { + public getBody(params: INotificationMessageBodyParams): string { + return ""; + } + + public getTitle(params: INotificationMessageTitleParams): string { + return ""; + } + + public getUrl(params: INotificationMessageUrlParams): string { + return ""; + } +} + +export const NotificationReviewRequestMessage = NotificationMessage.createImplementation({ + implementation: NotificationReviewRequestMessageImpl, + dependencies: [] +}); diff --git a/packages/api-workflows/src/domain/notification/messages/index.ts b/packages/api-workflows/src/domain/notification/messages/index.ts new file mode 100644 index 00000000000..b9d6922648b --- /dev/null +++ b/packages/api-workflows/src/domain/notification/messages/index.ts @@ -0,0 +1 @@ +export * from "./NotificationMessageOnRequestReview.js"; diff --git a/packages/api-workflows/src/domain/notifications/abstractions.ts b/packages/api-workflows/src/domain/notifications/abstractions.ts deleted file mode 100644 index d209f4a2ca5..00000000000 --- a/packages/api-workflows/src/domain/notifications/abstractions.ts +++ /dev/null @@ -1,103 +0,0 @@ -import { createAbstraction } from "@webiny/feature/api"; - -export interface INotificationTypeMessage { - title: string; - body: string; - url: string; -} - -export const NotificationTypeMessageOnRequestReview = createAbstraction( - "WorkflowNotificationTypeMessageOnRequestReview" -); - -export namespace NotificationTypeMessageOnRequestReview { - export type Interface = INotificationTypeMessage; -} - -export const NotificationTypeMessageOnRequestReviewCancel = - createAbstraction( - "WorkflowNotificationTypeMessageOnRequestReviewCancel" - ); - -export namespace NotificationTypeMessageOnRequestReviewCancel { - export type Interface = INotificationTypeMessage; -} - -export const NotificationTypeMessageOnReviewStepStart = createAbstraction( - "WorkflowNotificationTypeMessageOnReviewStepStart" -); - -export namespace NotificationTypeMessageOnReviewStepStart { - export type Interface = INotificationTypeMessage; -} - -export const NotificationTypeMessageOnReviewStepApprove = - createAbstraction( - "WorkflowNotificationTypeMessageOnReviewStepApprove" - ); - -export namespace NotificationTypeMessageOnReviewStepApprove { - export type Interface = INotificationTypeMessage; -} - -export const NotificationTypeMessageOnReviewReject = createAbstraction( - "WorkflowNotificationTypeMessageOnReviewReject" -); - -export namespace NotificationTypeMessageOnReviewReject { - export type Interface = INotificationTypeMessage; -} - -export const NotificationTypeMessageOnReviewApprove = createAbstraction( - "WorkflowNotificationTypeMessageOnReviewApprove" -); - -export namespace NotificationTypeMessageOnReviewApprove { - export type Interface = INotificationTypeMessage; -} - -export type INotificationTypes = - | NotificationTypeMessageOnRequestReview.Interface - | NotificationTypeMessageOnRequestReviewCancel.Interface - | NotificationTypeMessageOnReviewStepStart.Interface - | NotificationTypeMessageOnReviewStepApprove.Interface - | NotificationTypeMessageOnReviewReject.Interface - | NotificationTypeMessageOnReviewApprove.Interface; - -export interface INotificationTypeMessages { - /** - * User requests a review for a content. - */ - onRequestReview: NotificationTypeMessageOnRequestReview.Interface; - /** - * User cancels a review for a content. - */ - onRequestReviewCancel: NotificationTypeMessageOnRequestReviewCancel.Interface; - /** - * Reviewer starts a review step. - */ - onReviewStepStart: NotificationTypeMessageOnReviewStepStart.Interface; - /** - * Reviewer approves a review step - there are more steps after this one. - */ - onReviewStepApprove: NotificationTypeMessageOnReviewStepApprove.Interface; - /** - * Reviewer rejects a review - no step specific reject because as soon as step is rejected, whole review is rejected. - */ - onReviewReject: NotificationTypeMessageOnReviewReject.Interface; - /** - * Reviewer approves a final review step. - */ - onReviewApprove: NotificationTypeMessageOnReviewApprove.Interface; -} - -export interface INotificationType { - id: string; - title: string; -} - -export const NotificationType = createAbstraction("WorkflowNotificationType"); - -export namespace NotificationType { - export type Interface = INotificationType; -} diff --git a/packages/api-workflows/src/features/notifications/ListNotificationTypes/ListNotificationTypesRepository.ts b/packages/api-workflows/src/features/notification/ListNotificationTypes/ListNotificationTypesRepository.ts similarity index 74% rename from packages/api-workflows/src/features/notifications/ListNotificationTypes/ListNotificationTypesRepository.ts rename to packages/api-workflows/src/features/notification/ListNotificationTypes/ListNotificationTypesRepository.ts index f6ca113c8d5..e1b64f8ad39 100644 --- a/packages/api-workflows/src/features/notifications/ListNotificationTypes/ListNotificationTypesRepository.ts +++ b/packages/api-workflows/src/features/notification/ListNotificationTypes/ListNotificationTypesRepository.ts @@ -1,11 +1,11 @@ import { Result } from "@webiny/feature/api"; import { ListNotificationTypesRepository as Repository } from "./abstractions.js"; -import { NotificationTransport } from "~/features/notifications/NotificationTransport/index.js"; +import { NotificationAdapter } from "~/domain/notification/abstractions.js"; class ListNotificationTypesRepositoryImpl implements Repository.Interface { private readonly types; - public constructor(types: NotificationTransport.Interface[]) { + public constructor(types: NotificationAdapter.Interface[]) { this.types = types; } @@ -23,5 +23,5 @@ class ListNotificationTypesRepositoryImpl implements Repository.Interface { export const ListNotificationTypesRepository = Repository.createImplementation({ implementation: ListNotificationTypesRepositoryImpl, - dependencies: [[NotificationTransport, { multiple: true }]] + dependencies: [[NotificationAdapter, { multiple: true }]] }); diff --git a/packages/api-workflows/src/features/notifications/ListNotificationTypes/ListNotificationTypesUseCase.ts b/packages/api-workflows/src/features/notification/ListNotificationTypes/ListNotificationTypesUseCase.ts similarity index 99% rename from packages/api-workflows/src/features/notifications/ListNotificationTypes/ListNotificationTypesUseCase.ts rename to packages/api-workflows/src/features/notification/ListNotificationTypes/ListNotificationTypesUseCase.ts index f41bb7ea082..f280100530d 100644 --- a/packages/api-workflows/src/features/notifications/ListNotificationTypes/ListNotificationTypesUseCase.ts +++ b/packages/api-workflows/src/features/notification/ListNotificationTypes/ListNotificationTypesUseCase.ts @@ -7,7 +7,7 @@ import { import { WORKFLOWS_PERMISSION } from "~/constants.js"; import type { IWorkflowsSecurityPermission } from "~/types.js"; import { WorkflowsSecurityPermissionAccessLevel } from "~/types.js"; -import { NotificationAuthorizedError } from "~/domain/notifications/errors.js"; +import { NotificationAuthorizedError } from "~/domain/notification/errors.js"; class ListNotificationTypesUseCaseImpl implements UseCase.Interface { public constructor( diff --git a/packages/api-workflows/src/features/notifications/ListNotificationTypes/abstractions.ts b/packages/api-workflows/src/features/notification/ListNotificationTypes/abstractions.ts similarity index 93% rename from packages/api-workflows/src/features/notifications/ListNotificationTypes/abstractions.ts rename to packages/api-workflows/src/features/notification/ListNotificationTypes/abstractions.ts index 49b113dcf22..4c49f6ad0b4 100644 --- a/packages/api-workflows/src/features/notifications/ListNotificationTypes/abstractions.ts +++ b/packages/api-workflows/src/features/notification/ListNotificationTypes/abstractions.ts @@ -1,7 +1,7 @@ import type { Result } from "@webiny/feature/api"; import { createAbstraction } from "@webiny/feature/api"; -import type { INotificationType } from "~/domain/notifications/abstractions.js"; -import { NotificationAuthorizedError } from "~/domain/notifications/errors.js"; +import type { INotificationType } from "~/domain/notification/abstractions.js"; +import { NotificationAuthorizedError } from "~/domain/notification/errors.js"; /** * ListNotificationTypes use case interface diff --git a/packages/api-workflows/src/features/notifications/ListNotificationTypes/feature.ts b/packages/api-workflows/src/features/notification/ListNotificationTypes/feature.ts similarity index 100% rename from packages/api-workflows/src/features/notifications/ListNotificationTypes/feature.ts rename to packages/api-workflows/src/features/notification/ListNotificationTypes/feature.ts diff --git a/packages/api-workflows/src/features/notifications/ListNotificationTypes/index.ts b/packages/api-workflows/src/features/notification/ListNotificationTypes/index.ts similarity index 100% rename from packages/api-workflows/src/features/notifications/ListNotificationTypes/index.ts rename to packages/api-workflows/src/features/notification/ListNotificationTypes/index.ts diff --git a/packages/api-workflows/src/features/notifications/NotificationTransport/MailNotificationTransport.ts b/packages/api-workflows/src/features/notification/NotificationMailAdapter/NotificationMailAdapter.ts similarity index 67% rename from packages/api-workflows/src/features/notifications/NotificationTransport/MailNotificationTransport.ts rename to packages/api-workflows/src/features/notification/NotificationMailAdapter/NotificationMailAdapter.ts index e6614dd9ce3..590c316350d 100644 --- a/packages/api-workflows/src/features/notifications/NotificationTransport/MailNotificationTransport.ts +++ b/packages/api-workflows/src/features/notification/NotificationMailAdapter/NotificationMailAdapter.ts @@ -1,7 +1,8 @@ import { MailerService } from "@webiny/api-mailer"; -import { NotificationTransport } from "./abstractions.js"; +import type { NotificationMessageBodyConverter } from "~/domain/notification/abstractions.js"; +import { NotificationAdapter } from "~/domain/notification/abstractions.js"; -class MailNotificationTransportImpl implements NotificationTransport.Interface { +class NotificationMailAdapterImpl implements NotificationAdapter.Interface { public readonly id = "e-mail"; public readonly title = "E-Mail"; @@ -11,7 +12,11 @@ class MailNotificationTransportImpl implements NotificationTransport.Interface { this.mailer = mailer; } - public async send(params: NotificationTransport.SendParams): Promise { + public getMessageBodyConverter(): NotificationMessageBodyConverter.Interface | null { + return null; + } + + public async send(params: NotificationAdapter.Params): Promise { const { users, message } = params; const bcc = users @@ -47,7 +52,7 @@ class MailNotificationTransportImpl implements NotificationTransport.Interface { } } -export const MailNotificationTransport = NotificationTransport.createImplementation({ - implementation: MailNotificationTransportImpl, +export const NotificationMailAdapter = NotificationAdapter.createImplementation({ + implementation: NotificationMailAdapterImpl, dependencies: [MailerService] }); diff --git a/packages/api-workflows/src/features/notification/NotificationMailAdapter/feature.ts b/packages/api-workflows/src/features/notification/NotificationMailAdapter/feature.ts new file mode 100644 index 00000000000..c88bc4c7c48 --- /dev/null +++ b/packages/api-workflows/src/features/notification/NotificationMailAdapter/feature.ts @@ -0,0 +1,9 @@ +import { createFeature } from "@webiny/feature/api"; +import { NotificationMailAdapter } from "./NotificationMailAdapter.js"; + +export const NotificationMailAdapterFeature = createFeature({ + name: "WorkflowNotifications/NotificationMailAdapter", + register(container) { + container.register(NotificationMailAdapter); + } +}); diff --git a/packages/api-workflows/src/features/notification/NotificationMailAdapter/index.ts b/packages/api-workflows/src/features/notification/NotificationMailAdapter/index.ts new file mode 100644 index 00000000000..a1e0f4c14b1 --- /dev/null +++ b/packages/api-workflows/src/features/notification/NotificationMailAdapter/index.ts @@ -0,0 +1 @@ +export { NotificationMailAdapterFeature } from "./feature.js"; diff --git a/packages/api-workflows/src/features/notification/NotifyUsers/GetWorkflow.ts b/packages/api-workflows/src/features/notification/NotifyUsers/GetWorkflow.ts new file mode 100644 index 00000000000..7c992a414d9 --- /dev/null +++ b/packages/api-workflows/src/features/notification/NotifyUsers/GetWorkflow.ts @@ -0,0 +1,32 @@ +import { IWorkflow } from "~/domain/workflow/abstractions.js"; +import { GetWorkflow as GetWorkflowAbstraction } from "./abstractions.js"; +import { GetWorkflowRepository } from "~/features/workflow/GetWorkflow/index.js"; +import { Logger } from "@webiny/api-core/features/logger/index.js"; + +class GetWorkflowImpl implements GetWorkflowAbstraction.Interface { + public constructor( + private logger: Logger.Interface, + private getWorkflowRepository: GetWorkflowRepository.Interface + ) {} + + public async execute(params: GetWorkflowAbstraction.Params): Promise { + const result = await this.getWorkflowRepository.execute({ + id: params.id, + app: params.app + }); + if (result.isFail()) { + this.logger.error( + `Could not load workflow "${params.id}"/${params.app}. More in a log below this line.` + ); + this.logger.log(result.error); + return null; + } + + return result.value; + } +} + +export const GetWorkflow = GetWorkflowAbstraction.createImplementation({ + implementation: GetWorkflowImpl, + dependencies: [Logger, GetWorkflowRepository] +}); diff --git a/packages/api-workflows/src/features/notification/NotifyUsers/NotifyUsersOnStateApproveStep.ts b/packages/api-workflows/src/features/notification/NotifyUsers/NotifyUsersOnStateApproveStep.ts new file mode 100644 index 00000000000..582297bf86a --- /dev/null +++ b/packages/api-workflows/src/features/notification/NotifyUsers/NotifyUsersOnStateApproveStep.ts @@ -0,0 +1,25 @@ +import { WorkflowStateApproveStepHandler } from "~/features/workflowState/ApproveWorkflowStateStep/events.js"; +import { GetWorkflow } from "~/features/notification/NotifyUsers/abstractions.js"; + +class NotifyUsersOnStateApproveStepImpl implements WorkflowStateApproveStepHandler.Interface { + public constructor(private getWorkflow: GetWorkflow.Interface) {} + + public async handle(event: WorkflowStateApproveStepHandler.Event): Promise { + const { state } = event.payload; + + const workflow = await this.getWorkflow.execute({ + id: state.workflowId, + app: state.app + }); + if (!workflow) { + return; + } + + + } +} + +export const NotifyUsersOnStateApproveStep = WorkflowStateApproveStepHandler.createImplementation({ + implementation: NotifyUsersOnStateApproveStepImpl, + dependencies: [GetWorkflow] +}); diff --git a/packages/api-workflows/src/features/notification/NotifyUsers/NotifyUsersOnStateCancel.ts b/packages/api-workflows/src/features/notification/NotifyUsers/NotifyUsersOnStateCancel.ts new file mode 100644 index 00000000000..6a698d1220d --- /dev/null +++ b/packages/api-workflows/src/features/notification/NotifyUsers/NotifyUsersOnStateCancel.ts @@ -0,0 +1,12 @@ +import { WorkflowStateCancelHandler } from "~/features/workflowState/CancelWorkflowState/events.js"; + +class NotifyUsersOnStateCancelImpl implements WorkflowStateCancelHandler.Interface { + public async handle(event: WorkflowStateCancelHandler.Event): Promise { + const { state } = event.payload; + } +} + +export const NotifyUsersOnStateCancel = WorkflowStateCancelHandler.createImplementation({ + implementation: NotifyUsersOnStateCancelImpl, + dependencies: [] +}); diff --git a/packages/api-workflows/src/features/notification/NotifyUsers/NotifyUsersOnStateCreate.ts b/packages/api-workflows/src/features/notification/NotifyUsers/NotifyUsersOnStateCreate.ts new file mode 100644 index 00000000000..b40e289abf2 --- /dev/null +++ b/packages/api-workflows/src/features/notification/NotifyUsers/NotifyUsersOnStateCreate.ts @@ -0,0 +1,49 @@ +import { WorkflowStateAfterCreateHandler } from "~/features/workflowState/CreateWorkflowState/index.js"; +import { GetWorkflow, TriggerAdapters } from "~/features/notification/NotifyUsers/abstractions.js"; +import { NotificationAdapter } from "~/domain/notification/abstractions.js"; +import type { NonEmptyArray } from "@webiny/api/types.js"; + +/** + * This handler is responsible for notifying users when a new workflow state is created - a user requests a review of the entry. + */ +class NotifyUsersOnStateCreateImpl implements WorkflowStateAfterCreateHandler.Interface { + public constructor( + private getWorkflow: GetWorkflow.Interface, + private triggerAdapters: TriggerAdapters.Interface + ) {} + + public async handle(event: WorkflowStateAfterCreateHandler.Event): Promise { + /** + * No point triggering adapters if none are registered. + */ + if (this.triggerAdapters.hasAny() === false) { + return; + } + const { state } = event.payload; + + const workflow = await this.getWorkflow.execute({ + id: state.workflowId, + app: state.app + }); + if (!workflow) { + return; + } + // need to find all users that need to be notified. + const users: NotificationAdapter.User[] = []; + if (users.length === 0) { + return; + } + // and then trigger all adapters + await this.triggerAdapters.execute({ + state, + workflow, + users: users as NonEmptyArray, + message + }); + } +} + +export const NotifyUsersOnStateCreate = WorkflowStateAfterCreateHandler.createImplementation({ + implementation: NotifyUsersOnStateCreateImpl, + dependencies: [GetWorkflow, TriggerAdapters] +}); diff --git a/packages/api-workflows/src/features/notification/NotifyUsers/NotifyUsersOnStateDelete.ts b/packages/api-workflows/src/features/notification/NotifyUsers/NotifyUsersOnStateDelete.ts new file mode 100644 index 00000000000..17f08c7e513 --- /dev/null +++ b/packages/api-workflows/src/features/notification/NotifyUsers/NotifyUsersOnStateDelete.ts @@ -0,0 +1,12 @@ +import { WorkflowStateAfterDeleteHandler } from "~/features/workflowState/DeleteTargetWorkflowState/index.js"; + +class NotifyUsersOnStateDeleteImpl implements WorkflowStateAfterDeleteHandler.Interface { + public async handle(event: WorkflowStateAfterDeleteHandler.Event): Promise { + const { state } = event.payload; + } +} + +export const NotifyUsersOnStateDelete = WorkflowStateAfterDeleteHandler.createImplementation({ + implementation: NotifyUsersOnStateDeleteImpl, + dependencies: [] +}); diff --git a/packages/api-workflows/src/features/notification/NotifyUsers/NotifyUsersOnStateReject.ts b/packages/api-workflows/src/features/notification/NotifyUsers/NotifyUsersOnStateReject.ts new file mode 100644 index 00000000000..4a5d54d995c --- /dev/null +++ b/packages/api-workflows/src/features/notification/NotifyUsers/NotifyUsersOnStateReject.ts @@ -0,0 +1,12 @@ +import { WorkflowStateRejectHandler } from "~/features/workflowState/RejectWorkflowStateStep/events.js"; + +class NotifyUsersOnStateRejectImpl implements WorkflowStateRejectHandler.Interface { + public async handle(event: WorkflowStateRejectHandler.Event): Promise { + const { state } = event.payload; + } +} + +export const NotifyUsersOnStateReject = WorkflowStateRejectHandler.createImplementation({ + implementation: NotifyUsersOnStateRejectImpl, + dependencies: [] +}); diff --git a/packages/api-workflows/src/features/notification/NotifyUsers/NotifyUsersOnStateStartStep.ts b/packages/api-workflows/src/features/notification/NotifyUsers/NotifyUsersOnStateStartStep.ts new file mode 100644 index 00000000000..dbbd39ca5b6 --- /dev/null +++ b/packages/api-workflows/src/features/notification/NotifyUsers/NotifyUsersOnStateStartStep.ts @@ -0,0 +1,12 @@ +import { WorkflowStateStartStepHandler } from "~/features/workflowState/StartWorkflowStateStep/events.js"; + +class NotifyUsersOnStateStartStepImpl implements WorkflowStateStartStepHandler.Interface { + public async handle(event: WorkflowStateStartStepHandler.Event): Promise { + const { state } = event.payload; + } +} + +export const NotifyUsersOnStateStartStep = WorkflowStateStartStepHandler.createImplementation({ + implementation: NotifyUsersOnStateStartStepImpl, + dependencies: [] +}); diff --git a/packages/api-workflows/src/features/notification/NotifyUsers/NotifyUsersOnStateTakeOverStep.ts b/packages/api-workflows/src/features/notification/NotifyUsers/NotifyUsersOnStateTakeOverStep.ts new file mode 100644 index 00000000000..4ca8753146e --- /dev/null +++ b/packages/api-workflows/src/features/notification/NotifyUsers/NotifyUsersOnStateTakeOverStep.ts @@ -0,0 +1,14 @@ +import { WorkflowStateTakeOverStepHandler } from "~/features/workflowState/TakeOverWorkflowStateStep/events.js"; + +class NotifyUsersOnStateTakeOverStepImpl implements WorkflowStateTakeOverStepHandler.Interface { + public async handle(event: WorkflowStateTakeOverStepHandler.Event): Promise { + const { state } = event.payload; + } +} + +export const NotifyUsersOnStateTakeOverStep = WorkflowStateTakeOverStepHandler.createImplementation( + { + implementation: NotifyUsersOnStateTakeOverStepImpl, + dependencies: [] + } +); diff --git a/packages/api-workflows/src/features/notification/NotifyUsers/TriggerAdapters.ts b/packages/api-workflows/src/features/notification/NotifyUsers/TriggerAdapters.ts new file mode 100644 index 00000000000..03bc1ba2e2e --- /dev/null +++ b/packages/api-workflows/src/features/notification/NotifyUsers/TriggerAdapters.ts @@ -0,0 +1,39 @@ +import { Result } from "@webiny/feature/api"; +import { NotificationAdapter } from "~/domain/notification/abstractions.js"; +import { + type ITriggerAdaptersParams, + TriggerAdapters as TriggerAdaptersAbstraction, + type TriggerAdaptersResult +} from "./abstractions.js"; + +class TriggerAdaptersImpl implements TriggerAdaptersAbstraction.Interface { + public constructor(private adapters: NotificationAdapter.Interface[]) {} + + public hasAny(): boolean { + return this.adapters.length > 0; + } + + public async execute(params: ITriggerAdaptersParams): Promise { + const { users, message } = params; + // execute all adapters in parallel? + const promises = await Promise.all( + this.adapters.map(async adapter => { + try { + return await adapter.send({ + message, + users + }); + } catch (ex) { + return; + } + }) + ); + + return Result.ok(); + } +} + +export const TriggerAdapters = TriggerAdaptersAbstraction.createImplementation({ + implementation: TriggerAdaptersImpl, + dependencies: [[NotificationAdapter, { multiple: true }]] +}); diff --git a/packages/api-workflows/src/features/notification/NotifyUsers/abstractions.ts b/packages/api-workflows/src/features/notification/NotifyUsers/abstractions.ts new file mode 100644 index 00000000000..8fe72a84796 --- /dev/null +++ b/packages/api-workflows/src/features/notification/NotifyUsers/abstractions.ts @@ -0,0 +1,57 @@ +import { createAbstraction } from "@webiny/feature/createAbstraction.js"; +import type { IWorkflow } from "~/domain/workflow/abstractions.js"; +import type { IWorkflowState } from "~/domain/workflowState/abstractions.js"; +import type { Result } from "@webiny/feature/api/index.js"; +import type { NonEmptyArray } from "@webiny/api/types.js"; +import { + type INotificationAdapterMessage, + NotificationAdapter +} from "~/domain/notification/abstractions.js"; + +/** + * Get Workflow + */ +export interface IGetWorkflowExecuteParams { + id: string; + app: string; +} + +export interface IGetWorkflow { + /** + * This method should log errors internally and return null if the workflow is not found. + */ + execute(params: IGetWorkflowExecuteParams): Promise; +} + +export const GetWorkflow = createAbstraction("NotifyUsersGetWorkflow"); + +export namespace GetWorkflow { + export type Interface = IGetWorkflow; + export type Params = IGetWorkflowExecuteParams; +} + +/** + * Trigger Adapters + */ + +export interface ITriggerAdaptersParams { + workflow: IWorkflow; + state: IWorkflowState; + users: NonEmptyArray; + message: INotificationAdapterMessage; +} + +export type TriggerAdaptersResult = Promise>; + +export interface ITriggerAdapters { + hasAny(): boolean; + execute(params: ITriggerAdaptersParams): Promise; +} + +export const TriggerAdapters = createAbstraction("NotifyUsersTriggerAdapters"); + +export namespace TriggerAdapters { + export type Interface = ITriggerAdapters; + export type Params = ITriggerAdaptersParams; + export type Result = TriggerAdaptersResult; +} diff --git a/packages/api-workflows/src/features/notification/NotifyUsers/feature.ts b/packages/api-workflows/src/features/notification/NotifyUsers/feature.ts new file mode 100644 index 00000000000..cd5eb547d84 --- /dev/null +++ b/packages/api-workflows/src/features/notification/NotifyUsers/feature.ts @@ -0,0 +1,27 @@ +import { createFeature } from "@webiny/feature/api"; +import { NotifyUsersOnStateApproveStep } from "./NotifyUsersOnStateApproveStep.js"; +import { NotifyUsersOnStateCancel } from "./NotifyUsersOnStateCancel.js"; +import { NotifyUsersOnStateReject } from "./NotifyUsersOnStateReject.js"; +import { NotifyUsersOnStateCreate } from "./NotifyUsersOnStateCreate.js"; +import { NotifyUsersOnStateDelete } from "./NotifyUsersOnStateDelete.js"; +import { NotifyUsersOnStateStartStep } from "./NotifyUsersOnStateStartStep.js"; +import { NotifyUsersOnStateTakeOverStep } from "./NotifyUsersOnStateTakeOverStep.js"; +import { TriggerAdapters } from "./TriggerAdapters.js"; +import { GetWorkflow } from "./GetWorkflow.js"; + +export const NotifyUsersFeature = createFeature({ + name: "WorkflowNotifications/NotifyUsers", + register(container) { + // helpers for event handlers + container.register(TriggerAdapters); + container.register(GetWorkflow); + // event handlerrs + container.register(NotifyUsersOnStateCreate); + container.register(NotifyUsersOnStateDelete); + container.register(NotifyUsersOnStateStartStep); + container.register(NotifyUsersOnStateTakeOverStep); + container.register(NotifyUsersOnStateApproveStep); + container.register(NotifyUsersOnStateReject); + container.register(NotifyUsersOnStateCancel); + } +}); diff --git a/packages/api-workflows/src/features/notification/NotifyUsers/index.ts b/packages/api-workflows/src/features/notification/NotifyUsers/index.ts new file mode 100644 index 00000000000..879b45a068a --- /dev/null +++ b/packages/api-workflows/src/features/notification/NotifyUsers/index.ts @@ -0,0 +1 @@ +export * from "./feature.js"; diff --git a/packages/api-workflows/src/features/notifications/NotificationTransport/abstractions.ts b/packages/api-workflows/src/features/notifications/NotificationTransport/abstractions.ts deleted file mode 100644 index 6ec6f738ee5..00000000000 --- a/packages/api-workflows/src/features/notifications/NotificationTransport/abstractions.ts +++ /dev/null @@ -1,32 +0,0 @@ -import { createAbstraction } from "@webiny/feature/api"; -import type { - INotificationType, - INotificationTypeMessage -} from "~/domain/notifications/abstractions.js"; -import type { NonEmptyArray } from "@webiny/api/types.js"; - -export interface INotificationTransportUser { - id: string; - email: string; - displayName: string; -} - -export interface INotificationTransportSendParams { - users: NonEmptyArray; - message: INotificationTypeMessage; -} - -export interface INotificationTransport { - id: INotificationType["id"]; - title: INotificationType["title"]; - send(params: INotificationTransportSendParams): Promise; -} - -export const NotificationTransport = createAbstraction( - "WorkflowNotificationTransport" -); - -export namespace NotificationTransport { - export type Interface = INotificationTransport; - export type SendParams = INotificationTransportSendParams; -} diff --git a/packages/api-workflows/src/features/notifications/NotificationTransport/feature.ts b/packages/api-workflows/src/features/notifications/NotificationTransport/feature.ts deleted file mode 100644 index 37cee5724cc..00000000000 --- a/packages/api-workflows/src/features/notifications/NotificationTransport/feature.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { createFeature } from "@webiny/feature/api"; -import { MailNotificationTransport } from "./MailNotificationTransport.js"; - -export const NotificationTransportFeature = createFeature({ - name: "WorkflowNotifications/NotificationTransport", - register(container) { - container.register(MailNotificationTransport); - } -}); diff --git a/packages/api-workflows/src/features/notifications/NotificationTransport/index.ts b/packages/api-workflows/src/features/notifications/NotificationTransport/index.ts deleted file mode 100644 index d0ce4dd6332..00000000000 --- a/packages/api-workflows/src/features/notifications/NotificationTransport/index.ts +++ /dev/null @@ -1,7 +0,0 @@ -export { NotificationTransportFeature } from "./feature.js"; -export { NotificationTransport } from "./abstractions.js"; -export type { - INotificationTransportSendParams, - INotificationTransport, - INotificationTransportUser -} from "./abstractions.js"; diff --git a/packages/api-workflows/src/graphql/notifications.ts b/packages/api-workflows/src/graphql/notifications.ts index e65882ffd73..d0528d581d4 100644 --- a/packages/api-workflows/src/graphql/notifications.ts +++ b/packages/api-workflows/src/graphql/notifications.ts @@ -1,5 +1,5 @@ import { GraphQLSchemaPlugin, resolveList } from "@webiny/handler-graphql"; -import { ListNotificationTypesUseCase } from "~/features/notifications/ListNotificationTypes/index.js"; +import { ListNotificationTypesUseCase } from "~/features/notification/ListNotificationTypes/index.js"; export const createNotificationsGraphQL = () => { return new GraphQLSchemaPlugin({ diff --git a/packages/api-workflows/src/index.ts b/packages/api-workflows/src/index.ts index 468e55a6667..e4e124b76ac 100644 --- a/packages/api-workflows/src/index.ts +++ b/packages/api-workflows/src/index.ts @@ -35,8 +35,9 @@ import { ApproveWorkflowStateStepFeature } from "~/features/workflowState/Approv import { RejectWorkflowStateStepFeature } from "~/features/workflowState/RejectWorkflowStateStep/feature.js"; import { TakeOverWorkflowStateStepFeature } from "~/features/workflowState/TakeOverWorkflowStateStep/feature.js"; import { GetUserTeamsFeature } from "~/features/internal/GetUserTeams/feature.js"; -import { ListNotificationTypesFeature } from "~/features/notifications/ListNotificationTypes/index.js"; -import { NotificationTransportFeature } from "./features/notifications/NotificationTransport/index.js"; +import { ListNotificationTypesFeature } from "~/features/notification/ListNotificationTypes/index.js"; +import { NotificationMailAdapterFeature } from "./features/notification/NotificationMailAdapter/index.js"; +import { NotifyUsersFeature } from "./features/notification/NotifyUsers/index.js"; import { createNotificationsGraphQL } from "~/graphql/notifications.js"; export const createWorkflows = () => { @@ -77,7 +78,8 @@ export const createWorkflows = () => { // Register notification features ListNotificationTypesFeature.register(context.container); - NotificationTransportFeature.register(context.container); + NotificationMailAdapterFeature.register(context.container); + NotifyUsersFeature.register(context.container); // Register workflow features GetWorkflowFeature.register(context.container); diff --git a/packages/webiny/src/global.d.ts b/packages/webiny/src/global.d.ts new file mode 100644 index 00000000000..7b4a6078d99 --- /dev/null +++ b/packages/webiny/src/global.d.ts @@ -0,0 +1,7 @@ +/** + * Global type augmentations for Webiny. + * This file is automatically included in any TypeScript project that uses Webiny. + * + * This file ensures all type augmentations are loaded without requiring explicit imports. + */ +import "@webiny/tasks/global.js";