Files
cal.diy/packages/trpc/server/routers/viewer/workflows.tsx
T
+2 eeabb53c82 V2 Workflows (#4047)
* Fix breadcrumb colors

* HorizontalTabs

* Team List Item WIP

* Horizontal Tabs

* Cards

* Remove team list item WIP

* Login Page

* Add welcome back i118n

* EventType page work

* Update EventType Icons

* WIP Availability

* Horizontal Tab Work

* Add build command for in root

* Update build DIr/command

* Add Edit Button + change buttons to v2

* Availablitiy page

* Fix IPAD

* Make mobile look a little nicer

* WIP bookingshell

* Remove list items from breaking build

* Mian bulk of Booking Page.

* Few updates to components

* Fix chormatic feedback

* Fix banner

* Fix Empty Screen

* Text area + embded window fixes

* Semi fix avatar

* Troubleshoot container + Active on count

* Improve mobile

* NITS

* Fix padding on input

* Fix icons

* Starting to move event types settings to tabs

* Begin migration to single page form

* Single page tabs

* Limits Page

* Advanced tab

* Add RHF to dependancies

* Most of advanced tab

* Solved RHF mismtach

* Build fixes

* RHF conditionals fixes

* Improved legibility

* Major refactor/organisation into optional V2 UI

* Portal EditLocationModal

* Fix dialoug form

* Update imports

* Auto Animate + custom inputs WIP

* Custom Inputs

* WIP Apps

* Fixing stories imports

* Stripe app

* Remove duplicate dialog

* Remove duplicate dialog

* add Test action button + UI improvements

* add test action functionality

* Fix embed URL

* Fix app toggles + number of active apps

* Fix container padding on disabledBorder prop

* Removes strict

* add confirmation dialog before sending SMS

* code clean up

* show error message if test action fails

* disable test action button in edit mode

* fixes SMS testing

* use updated values

* fix wrongly updated data in useEffect

* fix typo

* code clean up

* EventType Team page WIP

* Fix embed

* NIT

* Add Darkmode gray color

* V2 Shell WIP

* setup folders for v2

* add lost translations from merge

* add all files to v2 + redesign /workflows

* use custom template as default template

* add first version of v2 for workflow editing page

* Fix headings on shell V2

* Fix mobile layout with V2 shell

* V2 create event type button

* Checked Team Select

* Hidden to happen on save - not on toggle

* Team Attendee Select animation

* Fix scheduling type and remove multi select label

* Fix overflow on teams url

* use fi icon for down arrow

* add v2 shell

* add trigger badge

* Even Type move order handles

* fix save button

* enable editing of workflow name

* improve delete workflow functionality

* adjust empty screen

* make trigger start with uppercase

* change trash icon

* Fix Embed TS errors

* Fix TS errors

* Fix Eslint errors

* Fix TS errors for UI

* Fix ESLINT error

* create component for time and timeUnit input/dropdown

* add workflows to v2 early access middleware

* fix type issues

* added SidebarCard for promo to v2 and storybook (#3906)

Co-authored-by: Julian Benegas <julianbenegas99@gmail.com>
Co-authored-by: Alan <alannnc@gmail.com>
Co-authored-by: sean-brydon <55134778+sean-brydon@users.noreply.github.com>

* Tooltip Provider - Wrapper due to dep upgrade

* public event type list darkmode

* V2 Color changes to public booking

* adjust delete and edit button

* create custom empty screen for worklows

* add workflow examples to empty page

* add loading state to button when creating first workflow

* Show action as workflow name when no name exists

* if no input leave name empty when updating workflow

* use reminder template when creating workflow

* improve time unit dropdown

* Remove unused component

* Fix typecheck

* add loading state to new workflow button

* add label before action dropdown

* fixing badges in worflow list

* use lighter color for untitled workflows

* fix not showing nr of active eventtypes in workflow list

* add workflows to event types

* implement v2 desing for AddActionDialog

* improve phone input

* rounded borders for phoneInput in add action dialog

* improve message input

* fix mobile view for editing page

* use md breakpoint for mobile view

* finish workflows in event type settings

* code cleanup

* code cleanup

* fix bug in testing workflow action

* add v2 design for addition inputs as variables dialog

* add V2 design for add variable dropdown

* add notification icons to trigger badge

* improve mobile version of workflows in eventtypes

* remove ring from time before input

* fix bug when workflow has no steps

* Final UI improvements/fixes

* code clean-up

* code clean-up

* code clean-up

* use v2 design for license required

* fix translation issues

* fix adding variables in different language in old design

* limit for pro users only for now

* fix import

* fix mobile view for empty screen

* ues destructive button color for deleting workfow

* remove padding at button of creation dialog

* V2 Settings - Security View (#4018)

* Create change password screen

* Add two factor auth screen

* Add two factor auth screen

* Remove header file

* Updates middleware and rewrites

* Adds Meta component to handle layout headings/metadata (#4021)

Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
Co-authored-by: zomars <zomars@me.com>

* fix: rate limit auth (#3820)

* fix: rate limit auth

* fix: replace lru-cache w memory-cache

* remove comments

* fix: yarn.lock

* fix: remove changes yarn lock

* fix: add missing EOL empty liune

* fix: move rate limiter so it kicks the last, limit to 10 tries per minute

* fix: move limiter w rest of code

* test: trying fix onboardong

* fix: undo changes in globalSetup.ts

* test: fix disable login for onboarding

* fix: use username instead of email for token check

* fix: tests

* fix: don't run on test

* fix: add missing comma

* fix: remove uniqueTokenPerInterval

* fix: add errorcode to packages lib auth

* Update packages/lib/rateLimit.ts

fix: improve readability

Co-authored-by: Omar López <zomars@me.com>

* Update packages/lib/rateLimit.ts

fix: no unnecessary any

Co-authored-by: Omar López <zomars@me.com>

* Update packages/lib/rateLimit.ts

fix: improve readability

Co-authored-by: Omar López <zomars@me.com>

* fix: rename interval -> intervalInMs

* fix: check user.email not username which could be empty

* fix: rateLimit update all naming

Co-authored-by: Agusti Fernandez Pardo <git@agusti.me>
Co-authored-by: Omar López <zomars@me.com>
Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
Co-authored-by: Peer Richelsen <peeroke@gmail.com>

* Adds deprecation comments

Co-authored-by: sean-brydon <55134778+sean-brydon@users.noreply.github.com>
Co-authored-by: Peer Richelsen <peeroke@gmail.com>
Co-authored-by: zomars <zomars@me.com>
Co-authored-by: CarinaWolli <wollencarina@gmail.com>
Co-authored-by: Hariom Balhara <hariombalhara@gmail.com>
Co-authored-by: Julian Benegas <julianbenegas99@gmail.com>
Co-authored-by: Alan <alannnc@gmail.com>
Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
Co-authored-by: Joe Au-Yeung <65426560+joeauyeung@users.noreply.github.com>
Co-authored-by: Agusti Fernandez Pardo <6601142+agustif@users.noreply.github.com>
Co-authored-by: Agusti Fernandez Pardo <git@agusti.me>
2022-08-31 13:42:37 -06:00

902 lines
27 KiB
TypeScript

import {
Prisma,
PrismaPromise,
WorkflowTemplates,
WorkflowActions,
WorkflowTriggerEvents,
BookingStatus,
WorkflowMethods,
TimeUnit,
} from "@prisma/client";
import { z } from "zod";
import dayjs from "@calcom/dayjs";
import {
WORKFLOW_TEMPLATES,
WORKFLOW_TRIGGER_EVENTS,
WORKFLOW_ACTIONS,
TIME_UNIT,
} from "@calcom/features/ee/workflows/lib/constants";
import {
deleteScheduledEmailReminder,
scheduleEmailReminder,
} from "@calcom/features/ee/workflows/lib/reminders/emailReminderManager";
import {
BookingInfo,
deleteScheduledSMSReminder,
scheduleSMSReminder,
} from "@calcom/features/ee/workflows/lib/reminders/smsReminderManager";
import { getErrorFromUnknown } from "@calcom/lib/errors";
import { getTranslation } from "@calcom/lib/server/i18n";
import { TRPCError } from "@trpc/server";
import { createProtectedRouter } from "../../createRouter";
export const workflowsRouter = createProtectedRouter()
.query("list", {
async resolve({ ctx }) {
const workflows = await ctx.prisma.workflow.findMany({
where: {
userId: ctx.user.id,
},
include: {
activeOn: {
select: {
eventType: {
select: {
id: true,
title: true,
},
},
},
},
steps: true,
},
orderBy: {
id: "asc",
},
});
return { workflows };
},
})
.query("get", {
input: z.object({
id: z.number(),
}),
async resolve({ ctx, input }) {
const workflow = await ctx.prisma.workflow.findFirst({
where: {
userId: ctx.user.id,
id: input.id,
},
select: {
id: true,
name: true,
time: true,
timeUnit: true,
activeOn: {
select: {
eventType: true,
},
},
trigger: true,
steps: {
orderBy: {
stepNumber: "asc",
},
},
},
});
if (!workflow) {
throw new TRPCError({
code: "UNAUTHORIZED",
});
}
return workflow;
},
})
.mutation("create", {
input: z.object({
name: z.string(),
trigger: z.enum(WORKFLOW_TRIGGER_EVENTS),
action: z.enum(WORKFLOW_ACTIONS),
timeUnit: z.enum(TIME_UNIT).optional(),
time: z.number().optional(),
sendTo: z.string().optional(),
}),
async resolve({ ctx, input }) {
const { name, trigger, action, timeUnit, time, sendTo } = input;
const userId = ctx.user.id;
try {
const workflow = await ctx.prisma.workflow.create({
data: {
name,
trigger,
userId,
timeUnit: time ? timeUnit : undefined,
time,
},
});
await ctx.prisma.workflowStep.create({
data: {
stepNumber: 1,
action,
workflowId: workflow.id,
sendTo,
},
});
return { workflow };
} catch (e) {
throw e;
}
},
})
.mutation("createV2", {
async resolve({ ctx }) {
const userId = ctx.user.id;
try {
const workflow = await ctx.prisma.workflow.create({
data: {
name: "",
trigger: WorkflowTriggerEvents.BEFORE_EVENT,
time: 24,
timeUnit: TimeUnit.HOUR,
userId,
},
});
await ctx.prisma.workflowStep.create({
data: {
stepNumber: 1,
action: WorkflowActions.EMAIL_HOST,
template: WorkflowTemplates.REMINDER,
workflowId: workflow.id,
},
});
return { workflow };
} catch (e) {
throw e;
}
},
})
.mutation("delete", {
input: z.object({
id: z.number(),
}),
async resolve({ ctx, input }) {
const { id } = input;
const workflowToDelete = await ctx.prisma.workflow.findFirst({
where: {
id,
userId: ctx.user.id,
},
});
if (workflowToDelete) {
const scheduledReminders = await ctx.prisma.workflowReminder.findMany({
where: {
workflowStep: {
workflowId: id,
},
scheduled: true,
NOT: {
referenceId: null,
},
},
});
scheduledReminders.forEach((reminder) => {
if (reminder.referenceId) {
if (reminder.method === WorkflowMethods.EMAIL) {
deleteScheduledEmailReminder(reminder.referenceId);
} else if (reminder.method === WorkflowMethods.SMS) {
deleteScheduledSMSReminder(reminder.referenceId);
}
}
});
await ctx.prisma.workflow.deleteMany({
where: {
userId: ctx.user.id,
id,
},
});
}
return {
id,
};
},
})
.mutation("update", {
input: z.object({
id: z.number(),
name: z.string(),
activeOn: z.number().array(),
steps: z
.object({
id: z.number(),
stepNumber: z.number(),
action: z.enum(WORKFLOW_ACTIONS),
workflowId: z.number(),
sendTo: z.string().optional().nullable(),
reminderBody: z.string().optional().nullable(),
emailSubject: z.string().optional().nullable(),
template: z.enum(WORKFLOW_TEMPLATES),
})
.array(),
trigger: z.enum(WORKFLOW_TRIGGER_EVENTS),
time: z.number().nullable(),
timeUnit: z.enum(TIME_UNIT).nullable(),
}),
async resolve({ input, ctx }) {
const { user } = ctx;
const { id, name, activeOn, steps, trigger, time, timeUnit } = input;
const userWorkflow = await ctx.prisma.workflow.findUnique({
where: {
id,
},
select: {
userId: true,
steps: true,
},
});
if (!userWorkflow || userWorkflow.userId !== user.id) throw new TRPCError({ code: "UNAUTHORIZED" });
const oldActiveOnEventTypes = await ctx.prisma.workflowsOnEventTypes.findMany({
where: {
workflowId: id,
},
select: {
eventTypeId: true,
},
});
const newActiveEventTypes = activeOn.filter((eventType) => {
if (
!oldActiveOnEventTypes ||
!oldActiveOnEventTypes
.map((oldEventType) => {
return oldEventType.eventTypeId;
})
.includes(eventType)
) {
return eventType;
}
});
//check if new event types belong to user
for (const newEventTypeId of newActiveEventTypes) {
const newEventType = await ctx.prisma.eventType.findFirst({
where: {
id: newEventTypeId,
},
include: {
team: {
include: {
members: true,
},
},
},
});
if (
newEventType &&
newEventType.userId !== user.id &&
newEventType?.team?.members.filter((membership) => {
membership.userId === user.id;
}).length
) {
throw new TRPCError({ code: "UNAUTHORIZED" });
}
}
//remove all scheduled Email and SMS reminders for eventTypes that are not active any more
const removedEventTypes = oldActiveOnEventTypes
.map((eventType) => {
return eventType.eventTypeId;
})
.filter((eventType) => {
if (!activeOn.includes(eventType)) {
return eventType;
}
});
const remindersToDeletePromise: PrismaPromise<
{
id: number;
referenceId: string | null;
method: string;
scheduled: boolean;
}[]
>[] = [];
removedEventTypes.forEach((eventTypeId) => {
const reminderToDelete = ctx.prisma.workflowReminder.findMany({
where: {
booking: {
eventTypeId: eventTypeId,
userId: ctx.user.id,
},
workflowStepId: {
in: userWorkflow.steps.map((step) => {
return step.id;
}),
},
},
select: {
id: true,
referenceId: true,
method: true,
scheduled: true,
},
});
remindersToDeletePromise.push(reminderToDelete);
});
const remindersToDelete = await Promise.all(remindersToDeletePromise);
const deleteReminderPromise: PrismaPromise<Prisma.BatchPayload>[] = [];
remindersToDelete.flat().forEach((reminder) => {
//already scheduled reminders
if (reminder.referenceId) {
if (reminder.method === WorkflowMethods.EMAIL) {
deleteScheduledEmailReminder(reminder.referenceId);
} else if (reminder.method === WorkflowMethods.SMS) {
deleteScheduledSMSReminder(reminder.referenceId);
}
}
const deleteReminder = ctx.prisma.workflowReminder.deleteMany({
where: {
id: reminder.id,
booking: {
userId: ctx.user.id,
},
},
});
deleteReminderPromise.push(deleteReminder);
});
await Promise.all(deleteReminderPromise);
//update active on & reminders for new eventTypes
await ctx.prisma.workflowsOnEventTypes.deleteMany({
where: {
workflowId: id,
},
});
let newEventTypes: number[] = [];
if (activeOn.length) {
if (trigger === WorkflowTriggerEvents.BEFORE_EVENT) {
newEventTypes = newActiveEventTypes;
}
if (newEventTypes.length > 0) {
//create reminders for all bookings with newEventTypes
const bookingsForReminders = await ctx.prisma.booking.findMany({
where: {
eventTypeId: { in: newEventTypes },
status: BookingStatus.ACCEPTED,
startTime: {
gte: new Date(),
},
},
include: {
attendees: true,
eventType: true,
user: true,
},
});
steps.forEach(async (step) => {
if (step.action !== WorkflowActions.SMS_ATTENDEE) {
//as we do not have attendees phone number (user is notified about that when setting this action)
bookingsForReminders.forEach(async (booking) => {
const bookingInfo = {
uid: booking.uid,
attendees: booking.attendees.map((attendee) => {
return { name: attendee.name, email: attendee.email, timeZone: attendee.timeZone };
}),
organizer: booking.user
? {
language: { locale: booking.user.locale || "" },
name: booking.user.name || "",
email: booking.user.email,
timeZone: booking.user.timeZone,
}
: { name: "", email: "", timeZone: "", language: { locale: "" } },
startTime: booking.startTime.toISOString(),
endTime: booking.endTime.toISOString(),
title: booking.title,
language: { locale: booking?.user?.locale || "" },
};
if (
step.action === WorkflowActions.EMAIL_HOST ||
step.action === WorkflowActions.EMAIL_ATTENDEE
) {
const sendTo =
step.action === WorkflowActions.EMAIL_HOST
? bookingInfo.organizer?.email
: bookingInfo.attendees[0].email;
await scheduleEmailReminder(
bookingInfo,
WorkflowTriggerEvents.BEFORE_EVENT,
step.action,
{
time,
timeUnit,
},
sendTo,
step.emailSubject || "",
step.reminderBody || "",
step.id,
step.template
);
} else if (step.action === WorkflowActions.SMS_NUMBER) {
await scheduleSMSReminder(
bookingInfo,
step.sendTo || "",
WorkflowTriggerEvents.BEFORE_EVENT,
step.action,
{
time,
timeUnit,
},
step.reminderBody || "",
step.id,
step.template
);
}
});
}
});
}
//create all workflow - eventtypes relationships
activeOn.forEach(async (eventTypeId) => {
await ctx.prisma.workflowsOnEventTypes.createMany({
data: {
workflowId: id,
eventTypeId,
},
});
});
}
userWorkflow.steps.map(async (oldStep) => {
const newStep = steps.filter((s) => s.id === oldStep.id)[0];
const remindersFromStep = await ctx.prisma.workflowReminder.findMany({
where: {
workflowStepId: oldStep.id,
},
include: {
booking: true,
},
});
//step was deleted
if (!newStep) {
//delete already scheduled reminders
if (remindersFromStep.length > 0) {
remindersFromStep.forEach((reminder) => {
if (reminder.referenceId) {
if (reminder.method === WorkflowMethods.EMAIL) {
deleteScheduledEmailReminder(reminder.referenceId);
} else if (reminder.method === WorkflowMethods.SMS) {
deleteScheduledSMSReminder(reminder.referenceId);
}
}
});
}
await ctx.prisma.workflowStep.delete({
where: {
id: oldStep.id,
},
});
//step was edited
} else if (JSON.stringify(oldStep) !== JSON.stringify(newStep)) {
await ctx.prisma.workflowStep.update({
where: {
id: oldStep.id,
},
data: {
action: newStep.action,
sendTo: newStep.action === WorkflowActions.SMS_NUMBER ? newStep.sendTo : null,
stepNumber: newStep.stepNumber,
workflowId: newStep.workflowId,
reminderBody: newStep.template === WorkflowTemplates.CUSTOM ? newStep.reminderBody : null,
emailSubject: newStep.template === WorkflowTemplates.CUSTOM ? newStep.emailSubject : null,
template: newStep.template,
},
});
//cancel all reminders of step and create new ones (not for newEventTypes)
const remindersToUpdate = remindersFromStep.filter((reminder) => {
if (reminder.booking?.eventTypeId && !newEventTypes.includes(reminder.booking?.eventTypeId)) {
return reminder;
}
});
remindersToUpdate.forEach(async (reminder) => {
if (reminder.referenceId) {
if (reminder.method === WorkflowMethods.EMAIL) {
deleteScheduledEmailReminder(reminder.referenceId);
} else if (reminder.method === WorkflowMethods.SMS) {
deleteScheduledSMSReminder(reminder.referenceId);
}
}
await ctx.prisma.workflowReminder.deleteMany({
where: {
id: reminder.id,
},
});
});
const eventTypesToUpdateReminders = activeOn.filter((eventTypeId) => {
if (!newEventTypes.includes(eventTypeId)) {
return eventTypeId;
}
});
if (eventTypesToUpdateReminders && trigger === WorkflowTriggerEvents.BEFORE_EVENT) {
const bookingsOfEventTypes = await ctx.prisma.booking.findMany({
where: {
eventTypeId: {
in: eventTypesToUpdateReminders,
},
status: BookingStatus.ACCEPTED,
startTime: {
gte: new Date(),
},
},
include: {
attendees: true,
eventType: true,
user: true,
},
});
bookingsOfEventTypes.forEach(async (booking) => {
const bookingInfo = {
uid: booking.uid,
attendees: booking.attendees.map((attendee) => {
return { name: attendee.name, email: attendee.email, timeZone: attendee.timeZone };
}),
organizer: booking.user
? {
language: { locale: booking.user.locale || "" },
name: booking.user.name || "",
email: booking.user.email,
timeZone: booking.user.timeZone,
}
: { name: "", email: "", timeZone: "", language: { locale: "" } },
startTime: booking.startTime.toISOString(),
endTime: booking.endTime.toISOString(),
title: booking.title,
language: { locale: booking?.user?.locale || "" },
};
if (
newStep.action === WorkflowActions.EMAIL_HOST ||
newStep.action === WorkflowActions.EMAIL_ATTENDEE
) {
const sendTo =
newStep.action === WorkflowActions.EMAIL_HOST
? bookingInfo.organizer?.email
: bookingInfo.attendees[0].email;
await scheduleEmailReminder(
bookingInfo,
WorkflowTriggerEvents.BEFORE_EVENT,
newStep.action,
{
time,
timeUnit,
},
sendTo,
newStep.emailSubject || "",
newStep.reminderBody || "",
newStep.id,
newStep.template
);
} else if (newStep.action === WorkflowActions.SMS_NUMBER) {
await scheduleSMSReminder(
bookingInfo,
newStep.sendTo || "",
WorkflowTriggerEvents.BEFORE_EVENT,
newStep.action,
{
time,
timeUnit,
},
newStep.reminderBody || "",
newStep.id || 0,
newStep.template
);
}
});
}
}
});
//added steps
const addedSteps = steps.map((s) => {
if (s.id <= 0) {
const { id, ...stepToAdd } = s;
if (stepToAdd) {
return stepToAdd;
}
}
});
if (addedSteps) {
const eventTypesToCreateReminders = activeOn.map((activeEventType) => {
if (activeEventType && !newEventTypes.includes(activeEventType)) {
return activeEventType;
}
});
addedSteps.forEach(async (step) => {
if (step) {
const createdStep = await ctx.prisma.workflowStep.create({
data: step,
});
if (
trigger === WorkflowTriggerEvents.BEFORE_EVENT &&
eventTypesToCreateReminders &&
step.action !== WorkflowActions.SMS_ATTENDEE
) {
const bookingsForReminders = await ctx.prisma.booking.findMany({
where: {
eventTypeId: { in: eventTypesToCreateReminders as number[] },
status: BookingStatus.ACCEPTED,
startTime: {
gte: new Date(),
},
},
include: {
attendees: true,
eventType: true,
user: true,
},
});
bookingsForReminders.forEach(async (booking) => {
const bookingInfo = {
uid: booking.uid,
attendees: booking.attendees.map((attendee) => {
return { name: attendee.name, email: attendee.email, timeZone: attendee.timeZone };
}),
organizer: booking.user
? {
name: booking.user.name || "",
email: booking.user.email,
timeZone: booking.user.timeZone,
language: { locale: booking.user.locale || "" },
}
: { name: "", email: "", timeZone: "", language: { locale: "" } },
startTime: booking.startTime.toISOString(),
endTime: booking.endTime.toISOString(),
title: booking.title,
language: { locale: booking?.user?.locale || "" },
};
if (
step.action === WorkflowActions.EMAIL_ATTENDEE ||
step.action === WorkflowActions.EMAIL_HOST
) {
const sendTo =
step.action === WorkflowActions.EMAIL_HOST
? bookingInfo.organizer?.email
: bookingInfo.attendees[0].email;
await scheduleEmailReminder(
bookingInfo,
trigger,
step.action,
{
time,
timeUnit,
},
sendTo,
step.emailSubject || "",
step.reminderBody || "",
createdStep.id,
step.template
);
} else if (step.action === WorkflowActions.SMS_NUMBER && step.sendTo) {
await scheduleSMSReminder(
bookingInfo,
step.sendTo,
WorkflowTriggerEvents.BEFORE_EVENT,
step.action,
{
time,
timeUnit,
},
step.reminderBody || "",
createdStep.id,
step.template
);
}
});
}
}
});
}
//update trigger, name, time, timeUnit
await ctx.prisma.workflow.update({
where: {
id,
},
data: {
name,
trigger,
time,
timeUnit,
},
});
const workflow = await ctx.prisma.workflow.findFirst({
where: {
id,
},
include: {
activeOn: {
select: {
eventType: true,
},
},
steps: true,
},
});
return {
workflow,
};
},
})
.mutation("testAction", {
input: z.object({
action: z.enum(WORKFLOW_ACTIONS),
emailSubject: z.string(),
reminderBody: z.string(),
template: z.enum(WORKFLOW_TEMPLATES),
sendTo: z.string().optional(),
}),
async resolve({ ctx, input }) {
const { action, emailSubject, reminderBody, template, sendTo } = input;
try {
const booking = await ctx.prisma.booking.findFirst({
orderBy: {
createdAt: "desc",
},
where: {
userId: ctx.user.id,
},
include: {
attendees: true,
user: true,
},
});
let evt: BookingInfo;
if (booking) {
evt = {
uid: booking?.uid,
attendees:
booking?.attendees.map((attendee) => {
return { name: attendee.name, email: attendee.email, timeZone: attendee.timeZone };
}) || [],
organizer: {
language: {
locale: booking?.user?.locale || "",
},
name: booking?.user?.name || "",
email: booking?.user?.email || "",
timeZone: booking?.user?.timeZone || "",
},
startTime: booking?.startTime.toISOString() || "",
endTime: booking?.endTime.toISOString() || "",
title: booking?.title || "",
location: booking?.location || null,
additionalNotes: booking?.description || null,
customInputs: booking?.customInputs,
};
} else {
//if no booking exists create an example booking
evt = {
attendees: [{ name: "John Doe", email: "john.doe@example.com", timeZone: "Europe/London" }],
organizer: {
language: {
locale: ctx.user.locale,
},
name: ctx.user.name || "",
email: ctx.user.email,
timeZone: ctx.user.timeZone,
},
startTime: dayjs().add(10, "hour").toISOString(),
endTime: dayjs().add(11, "hour").toISOString(),
title: "Example Booking",
location: "Office",
additionalNotes: "These are additional notes",
};
}
if (action === WorkflowActions.EMAIL_ATTENDEE || action === WorkflowActions.EMAIL_HOST) {
scheduleEmailReminder(
evt,
WorkflowTriggerEvents.NEW_EVENT,
action,
{ time: null, timeUnit: null },
ctx.user.email,
emailSubject,
reminderBody,
0,
template
);
return { message: "Notification sent" };
} else if (action === WorkflowActions.SMS_NUMBER && sendTo) {
scheduleSMSReminder(
evt,
sendTo,
WorkflowTriggerEvents.NEW_EVENT,
action,
{ time: null, timeUnit: null },
reminderBody,
0,
template
);
return { message: "Notification sent" };
}
return {
ok: false,
status: 500,
message: "Notification could not be sent",
};
} catch (_err) {
const error = getErrorFromUnknown(_err);
return {
ok: false,
status: 500,
message: error.message,
};
}
},
})
.mutation("activateEventType", {
input: z.object({
eventTypeId: z.number(),
workflowId: z.number(),
}),
async resolve({ ctx, input }) {
const { eventTypeId, workflowId } = input;
const eventType = await ctx.prisma.eventType.findFirst({
where: {
id: eventTypeId,
},
});
//check if event type is already active
const isActive = await ctx.prisma.workflowsOnEventTypes.findFirst({
where: {
workflowId,
eventTypeId,
},
});
if (isActive) {
await ctx.prisma.workflowsOnEventTypes.deleteMany({
where: {
workflowId,
eventTypeId,
},
});
} else {
await ctx.prisma.workflowsOnEventTypes.create({
data: {
workflowId,
eventTypeId,
},
});
}
},
});