This commit is contained in:
Alan 2022-03-31 10:57:06 -07:00
parent 46563754ff
commit e34473971a
8 changed files with 256 additions and 129 deletions

View File

@ -211,7 +211,7 @@ export const sendTeamInviteEmail = async (teamInviteEvent: TeamInvite) => {
});
};
export const sendRescheduleEmail = async (calEvent: CalendarEvent) => {
export const sendRequestRescheduleEmail = async (calEvent: CalendarEvent) => {
await new Promise((resolve, reject) => {
try {
const rescheduleEmail = new OrganizerRequestRescheduledEmail(calEvent);

View File

@ -1,9 +1,12 @@
import { BookingStatus } from "@prisma/client";
import dayjs from "dayjs";
import type { NextApiHandler, NextApiRequest, NextApiResponse } from "next";
import { getSession } from "next-auth/react";
import { z, ZodError } from "zod";
import { sendRescheduleEmail } from "@lib/emails/email-manager";
import CalendarDirector from "@calcom/core/builders/CalendarEvent/director";
import { sendRequestRescheduleEmail } from "@lib/emails/email-manager";
import OrganizerRequestRescheduledEmail from "@lib/emails/templates/organizer-request-reschedule-email";
import { getEventName } from "@lib/event";
import prisma from "@lib/prisma";
@ -14,9 +17,24 @@ const rescheduleSchema = z.object({
});
const handler = async (req: NextApiRequest, res: NextApiResponse) => {
const session = await getSession({ req });
const { bookingId, rescheduleReason: cancellationReason } = req.body;
console.log({ bookingId });
let userOwner;
try {
if (session?.user?.id) {
userOwner = await prisma.user.findUnique({
where: {
id: session.user.id,
},
select: {
locale: true,
},
});
} else {
// Throw error
}
const bookingToReschedule = await prisma.booking.findFirst({
select: {
id: true,
@ -24,6 +42,7 @@ const handler = async (req: NextApiRequest, res: NextApiResponse) => {
endTime: true,
userId: true,
attendees: true,
eventTypeId: true,
},
where: {
uid: bookingId,
@ -32,8 +51,13 @@ const handler = async (req: NextApiRequest, res: NextApiResponse) => {
},
},
});
if (bookingToReschedule) {
await prisma.booking.update({
if (bookingToReschedule && bookingToReschedule.eventTypeId) {
const event = await prisma.eventType.findFirst({
where: {
id: bookingToReschedule.eventTypeId,
},
});
const updatedBooking = await prisma.booking.update({
where: {
id: bookingToReschedule.id,
},
@ -52,8 +76,26 @@ const handler = async (req: NextApiRequest, res: NextApiResponse) => {
});
// Send email =================
const event = new CalendarEventBuilder({});
await sendRescheduleEmail(event);
const eventCalendar = new CalendarDirector({
booking: updatedBooking,
attendee: bookingToReschedule.attendees,
start: bookingToReschedule.startTime,
end: bookingToReschedule.endTime,
eventName: event?.eventName,
translationAttendees: userOwner.locale,
translationGuests: "en_us",
translationOwner: "en_us",
eventTypeId: bookingToReschedule.eventTypeId,
timeZone: event?.timeZone,
location: event?.locations,
notes: "",
customInputs: [],
});
const resultEvent = await eventCalendar.buildRequiredParams();
console.log(resultEvent);
console.log(resultEvent.attendees);
console.log(resultEvent.organizer);
await sendRequestRescheduleEmail(resultEvent);
}
return res.status(200).json(bookingToReschedule);

View File

@ -0,0 +1,19 @@
import { NextApiRequest } from "next";
import { NextApiResponse } from "next";
import { updateSubscription } from "@calcom/stripe/subscriptions";
import { getSession } from "@lib/auth";
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
if (req.method === "POST") {
const session = await getSession({ req });
const userId = session?.user?.id || 23;
const newPlanPriceId = req?.body?.newPlanPriceId;
if (userId) {
const result = await updateSubscription({ userId, newPlanPriceId });
return res.status(200).json(result);
}
}
return res.status(204);
}

View File

@ -109,10 +109,10 @@ export const createEvent = async (credential: Credential, calEvent: CalendarEven
const creationResult = calendar
? await calendar.createEvent(calEvent).catch((e) => {
log.error("createEvent failed", e, calEvent);
success = false;
return undefined;
})
log.error("createEvent failed", e, calEvent);
success = false;
return undefined;
})
: undefined;
return {
@ -136,10 +136,10 @@ export const updateEvent = async (
const updatedResult =
calendar && bookingRefUid
? await calendar.updateEvent(bookingRefUid, calEvent).catch((e) => {
log.error("updateEvent failed", e, calEvent);
success = false;
return undefined;
})
log.error("updateEvent failed", e, calEvent);
success = false;
return undefined;
})
: undefined;
return {

View File

@ -1,10 +1,11 @@
import { Booking, Prisma, SchedulingType } from "@prisma/client";
import dayjs from "dayjs";
import { Attendee } from "ics";
import short from "short-uuid";
import { v5 as uuidv5 } from "uuid";
import prisma from "@calcom/prisma";
import { Person } from "@calcom/types/Calendar";
import { CalendarEvent, Person } from "@calcom/types/Calendar";
import { CalendarEventBuilder } from ".";
import { ensureArray } from "../../../../apps/web/lib/ensureArray";
@ -29,13 +30,15 @@ const userSelect = Prisma.validator<Prisma.UserArgs>()({
type User = Prisma.UserGetPayload<typeof userSelect>;
interface ICalendarEventDirector extends Booking {
interface ICalendarEventDirector {
booking: CustomBookingFromSelect;
timeZone: any;
start: any;
end: any;
eventTypeId: any;
translationAttendees: string;
translationOwner: string;
translationOwner: any;
translationGuests: any;
attendee: any;
guestAttendees: any;
location: any;
@ -44,29 +47,38 @@ interface ICalendarEventDirector extends Booking {
customInputs: any;
}
export class CalendarEventDirector {
interface CustomBookingFromSelect {
id: number;
startTime: string;
endTime: string;
userId: string;
attendees: Partial<Attendee>[];
eventTypeId: number | null;
}
export default class CalendarEventDirector {
// Required props
private booking: Booking;
private booking: CustomBookingFromSelect;
private attendee: any;
private guestsAttendees: any;
private start: any;
private end: any;
private eventName: any;
private translationAttendees: string;
private translationGuests: string;
private translationAttendees: any;
private translationGuests: any;
private translationOwner: any;
private eventTypeId: any;
private timeZone: any;
private location: any;
private notes: string;
// private notes: string;
private customInputs: any;
// Internal control
private builder: CalendarEventBuilder;
private builder: CalendarEvent | null;
private eventType: any;
private users: any;
private organizer: any;
private invitee?: Person;
private invitee?: Person[];
private guests?: Person[];
private teamMembers?: Person[];
private attendeesList?: Person[];
@ -81,7 +93,7 @@ export class CalendarEventDirector {
private description?: string;
constructor(props: ICalendarEventDirector) {
this.booking = props;
this.booking = props.booking;
this.attendee = props.attendee;
this.guestsAttendees = props.guestAttendees;
this.start = props.start;
@ -93,8 +105,11 @@ export class CalendarEventDirector {
this.translationGuests = props.translationAttendees;
this.location = props.location;
this.eventName = props.eventName;
this.notes = props.notes;
// this.notes = props.notes;
this.customInputs = props.customInputs;
this.builder = null;
this.guests = [] as Person[];
this.attendeesList = [] as Person[];
}
public async buildRequiredParams() {
@ -102,28 +117,36 @@ export class CalendarEventDirector {
throw new Error(`Booking ${this.eventTypeId} failed`);
}
const eventTypeId = this.booking.eventTypeId;
await this.buildEventType(eventTypeId);
this.buildUsers(this.eventType.users);
this.buildOrganizerFromUserId(this.users[0]);
this.setOrganizerLanguage(this.organizer.language);
await this.buildUsers(this.eventType.users);
await this.buildOrganizerFromUserId(this.users[0].id);
await this.setOrganizerLanguage(this.organizer?.language);
this.buildInvitee();
this.buildGuest();
this.buildUID();
this.buildAttendeesList();
// this.buildTeam();
// this.buildUID();
this.buildDescription();
this.buildEventNameObject();
this.builder = new CalendarEventBuilder({
type: this.eventType.title,
title: getEventName(this.eventNameObject), //this needs to be either forced in english, or fetched for each attendee and organizer separately
title: this.eventNameObject ? getEventName(this.eventNameObject) : "Nameless Event", //this needs to be either forced in english, or fetched for each attendee and organizer separately
startTime: this.start,
endTime: this.end,
organizer: {
name: this.users[0].name || "Nameless",
email: this.users[0].email || "Email-less",
timeZone: this.users[0].timeZone,
name: this.organizer.name || "Nameless",
email: this.organizer.email || "Email-less",
timeZone: this.organizer.timeZone,
language: { translate: this.translationOwner, locale: this.organizer?.locale ?? "en" },
},
attendees: this.attendeesList,
});
attendees: this.attendeesList || [],
}).get();
return this.builder;
}
@ -131,36 +154,41 @@ export class CalendarEventDirector {
if (eventTypeId === null) {
throw new Error("Event Type Id not received");
}
const eventType = await prisma.eventType.findUnique({
rejectOnNotFound: true,
where: {
id: eventTypeId,
},
select: {
users: userSelect,
team: {
select: {
id: true,
name: true,
},
let eventType;
try {
eventType = await prisma.eventType.findUnique({
rejectOnNotFound: true,
where: {
id: eventTypeId,
},
title: true,
length: true,
eventName: true,
schedulingType: true,
periodType: true,
periodStartDate: true,
periodEndDate: true,
periodDays: true,
periodCountCalendarDays: true,
requiresConfirmation: true,
userId: true,
price: true,
currency: true,
metadata: true,
destinationCalendar: true,
},
});
select: {
users: userSelect,
team: {
select: {
id: true,
name: true,
},
},
title: true,
length: true,
eventName: true,
schedulingType: true,
periodType: true,
periodStartDate: true,
periodEndDate: true,
periodDays: true,
periodCountCalendarDays: true,
requiresConfirmation: true,
userId: true,
price: true,
currency: true,
metadata: true,
destinationCalendar: true,
},
});
} catch (error) {
console.log(error);
}
this.eventType = eventType;
}
@ -168,16 +196,20 @@ export class CalendarEventDirector {
private async buildUsers(_users: User[]) {
let users = _users;
if (!users.length && this.eventType.userId) {
const eventTypeUser = await prisma.user.findUnique({
where: {
id: this.eventType.userId,
},
...userSelect,
});
if (!eventTypeUser) {
throw new Error("eventTypeUser.notFound");
try {
const eventTypeUser = await prisma.user.findUnique({
where: {
id: this.eventType.userId,
},
...userSelect,
});
if (!eventTypeUser) {
throw new Error("eventTypeUser.notFound");
}
users.push(eventTypeUser);
} catch (error) {
console.log(error);
}
users.push(eventTypeUser);
}
if (this.eventType.schedulingType === SchedulingType.ROUND_ROBIN) {
@ -192,14 +224,19 @@ export class CalendarEventDirector {
}
private async buildOrganizerFromUserId(userId: number) {
const organizer = await prisma.user.findUnique({
where: {
id: userId,
},
// select: {
// locale: true,
// },
});
let organizer;
try {
organizer = await prisma.user.findUnique({
where: {
id: userId,
},
// select: {
// locale: true,
// },
});
} catch (error) {
console.log(error);
}
this.organizer = organizer;
}
@ -220,38 +257,43 @@ export class CalendarEventDirector {
}
private async getUserNameWithBookingCounts(eventTypeId: number, selectedUserNames: string[]) {
const users = await prisma.user.findMany({
where: {
username: { in: selectedUserNames },
eventTypes: {
some: {
id: eventTypeId,
let userNamesWithBookingCounts;
try {
const users = await prisma.user.findMany({
where: {
username: { in: selectedUserNames },
eventTypes: {
some: {
id: eventTypeId,
},
},
},
},
select: {
id: true,
username: true,
locale: true,
},
});
select: {
id: true,
username: true,
locale: true,
},
});
const userNamesWithBookingCounts = await Promise.all(
users.map(async (user) => ({
username: user.username,
bookingCount: await prisma.booking.count({
where: {
user: {
id: user.id,
userNamesWithBookingCounts = await Promise.all(
users.map(async (user) => ({
username: user.username,
bookingCount: await prisma.booking.count({
where: {
user: {
id: user.id,
},
startTime: {
gt: new Date(),
},
eventTypeId,
},
startTime: {
gt: new Date(),
},
eventTypeId,
},
}),
}))
);
}),
}))
);
} catch (error) {
console.log(error);
}
return userNamesWithBookingCounts;
}
@ -259,16 +301,17 @@ export class CalendarEventDirector {
private buildInvitee() {
this.invitee = [
{
email: this.attendee.email,
name: this.attendee.name,
timeZone: this.attendee.timeZone,
language: { translate: this.translationAttendees, locale: this.attendee.language ?? "en" },
email: this.attendee[0].email,
name: this.attendee[0].name,
timeZone: this.attendee[0].timeZone,
language: { translate: this.translationAttendees, locale: this.attendee[0].language ?? "en" },
},
];
}
private buildGuest() {
const guests = this.guestsAttendees?.map((currentGuest: any) => {
const guests = this.attendee.slice(1);
const guestsResult = this.attendeesList?.map((currentGuest: any) => {
return {
email: currentGuest,
name: "",
@ -276,7 +319,7 @@ export class CalendarEventDirector {
language: { translate: this.translationGuests, locale: "en" },
};
});
this.guests = guests;
this.guests = guestsResult;
}
private buildUID() {
@ -295,11 +338,17 @@ export class CalendarEventDirector {
}
private buildEventNameObject() {
// @NOTE: if multiple attendees should name be event with owner and attendeeList.map(item=>item.name).join(',')
const attendeeNames =
this.attendeesList && this.attendeesList?.length > 1
? this.attendeesList?.map((item) => item.name).join(", ")
: this.attendee[0].name;
console.log({ attendeeNames });
this.eventNameObject = {
attendeeName: this.eventName || "Nameless",
attendeeName: attendeeNames || "Nameless",
eventType: this.eventType.title,
eventName: this.eventType.eventName,
host: this.users[0].name || "Nameless",
host: this.organizer.name || "Nameless",
t: this.translationOwner,
};
}
@ -314,6 +363,19 @@ export class CalendarEventDirector {
}
}
private buildAttendeesList() {
this.attendeesList = [];
if (this.invitee) {
this.attendeesList.push(...this.invitee);
}
if (this.guests) {
this.attendeesList.push(...this.guests);
}
if (this.teamMembers) {
this.attendeesList.push(...this.teamMembers);
}
}
private isTimeInPast(time: string): boolean {
return dayjs(time).isBefore(new Date(), "day");
}

View File

@ -7,57 +7,61 @@ export class CalendarEventBuilder {
this.event = props;
}
public buildDescription(description: CalendarEvent["description"]) {
public get() {
return this.event;
}
public setDescription(description: CalendarEvent["description"]) {
this.event.description = description;
return this.event;
}
public buildTeam(team: CalendarEvent["team"]) {
public setTeam(team: CalendarEvent["team"]) {
this.event.team = team;
return this.event;
}
public buildLocation(location: CalendarEvent["location"]) {
public setLocation(location: CalendarEvent["location"]) {
this.event.location = location;
return this.event;
}
public buildConferenceData(conferenceData: CalendarEvent["conferenceData"]) {
public setConferenceData(conferenceData: CalendarEvent["conferenceData"]) {
this.event.conferenceData = conferenceData;
return this.event;
}
public buildAdditionalInformation(additionalInformation: CalendarEvent["additionInformation"]) {
public setAdditionalInformation(additionalInformation: CalendarEvent["additionInformation"]) {
this.event.additionInformation = additionalInformation;
return this.event;
}
public buildUID(UID: CalendarEvent["uid"]) {
public setUID(UID: CalendarEvent["uid"]) {
this.event.uid = UID;
return this.event;
}
public buildVideoCallData(videoCallData: CalendarEvent["videoCallData"]) {
public setVideoCallData(videoCallData: CalendarEvent["videoCallData"]) {
this.event.videoCallData = videoCallData;
return this.event;
}
public buildPaymentInfo(paymentInfo: CalendarEvent["paymentInfo"]) {
public setPaymentInfo(paymentInfo: CalendarEvent["paymentInfo"]) {
this.event.paymentInfo = paymentInfo;
return this.event;
}
public buildDestinationCalendar(destinationCalendar: CalendarEvent["destinationCalendar"]) {
public setDestinationCalendar(destinationCalendar: CalendarEvent["destinationCalendar"]) {
this.event.destinationCalendar = destinationCalendar;
return this.event;
}
public buildCancellationReason(cancellationReason: CalendarEvent["cancellationReason"]) {
public setCancellationReason(cancellationReason: CalendarEvent["cancellationReason"]) {
this.event.cancellationReason = cancellationReason;
return this.event;
}
public buildRejectionReason(rejectionReason: CalendarEvent["rejectionReason"]) {
public setRejectionReason(rejectionReason: CalendarEvent["rejectionReason"]) {
this.event.rejectionReason = rejectionReason;
return this.event;
}