173 lines
5.9 KiB
TypeScript
173 lines
5.9 KiB
TypeScript
import { SchedulingType } from "@prisma/client";
|
|
import { z } from "zod";
|
|
|
|
import { LocationType } from "@calcom/app-store/locations";
|
|
import EventManager from "@calcom/core/EventManager";
|
|
import dayjs from "@calcom/dayjs";
|
|
import { sendLocationChangeEmails } from "@calcom/emails";
|
|
import { parseRecurringEvent } from "@calcom/lib";
|
|
import logger from "@calcom/lib/logger";
|
|
import { getTranslation } from "@calcom/lib/server/i18n";
|
|
import type { AdditionalInformation, CalendarEvent } from "@calcom/types/Calendar";
|
|
|
|
import { TRPCError } from "@trpc/server";
|
|
|
|
import { createProtectedRouter } from "../../createRouter";
|
|
|
|
// Common data for all endpoints under webhook
|
|
const commonBookingSchema = z.object({
|
|
bookingId: z.number(),
|
|
});
|
|
|
|
export const bookingsRouter = createProtectedRouter()
|
|
.middleware(async ({ ctx, rawInput, next }) => {
|
|
// Endpoints that just read the logged in user's data - like 'list' don't necessary have any input
|
|
if (!rawInput) return next({ ctx: { ...ctx, booking: null } });
|
|
const webhookIdAndEventTypeId = commonBookingSchema.safeParse(rawInput);
|
|
if (!webhookIdAndEventTypeId.success) throw new TRPCError({ code: "PARSE_ERROR" });
|
|
|
|
const { bookingId } = webhookIdAndEventTypeId.data;
|
|
const booking = await ctx.prisma.booking.findFirst({
|
|
where: {
|
|
id: bookingId,
|
|
AND: [
|
|
{
|
|
OR: [
|
|
/* If user is organizer */
|
|
{ userId: ctx.user.id },
|
|
/* Or part of a collective booking */
|
|
{
|
|
eventType: {
|
|
schedulingType: SchedulingType.COLLECTIVE,
|
|
users: {
|
|
some: {
|
|
id: ctx.user.id,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
],
|
|
},
|
|
],
|
|
},
|
|
include: {
|
|
attendees: true,
|
|
eventType: true,
|
|
destinationCalendar: true,
|
|
references: true,
|
|
user: {
|
|
include: {
|
|
destinationCalendar: true,
|
|
credentials: true,
|
|
},
|
|
},
|
|
},
|
|
});
|
|
return next({ ctx: { ...ctx, booking } });
|
|
})
|
|
.middleware(async ({ ctx, next }) => {
|
|
// So TS doesn't compain in the previous middleware.
|
|
// This means the user doesn't have access to this booking
|
|
if (!ctx.booking) throw new TRPCError({ code: "UNAUTHORIZED" });
|
|
// Booking here is non-nullable anymore
|
|
return next({ ctx: { ...ctx, booking: ctx.booking } });
|
|
})
|
|
.mutation("editLocation", {
|
|
input: commonBookingSchema.extend({
|
|
newLocation: z.string().transform((val) => val || LocationType.Daily),
|
|
}),
|
|
async resolve({ ctx, input }) {
|
|
const { bookingId, newLocation: location } = input;
|
|
const { booking } = ctx;
|
|
|
|
try {
|
|
const organizer = await ctx.prisma.user.findFirst({
|
|
where: {
|
|
id: booking.userId || 0,
|
|
},
|
|
select: {
|
|
name: true,
|
|
email: true,
|
|
timeZone: true,
|
|
locale: true,
|
|
},
|
|
rejectOnNotFound: true,
|
|
});
|
|
|
|
const tOrganizer = await getTranslation(organizer.locale ?? "en", "common");
|
|
|
|
const attendeesListPromises = booking.attendees.map(async (attendee) => {
|
|
return {
|
|
name: attendee.name,
|
|
email: attendee.email,
|
|
timeZone: attendee.timeZone,
|
|
language: {
|
|
translate: await getTranslation(attendee.locale ?? "en", "common"),
|
|
locale: attendee.locale ?? "en",
|
|
},
|
|
};
|
|
});
|
|
|
|
const attendeesList = await Promise.all(attendeesListPromises);
|
|
|
|
const evt: CalendarEvent = {
|
|
title: booking.title || "",
|
|
type: (booking.eventType?.title as string) || booking?.title || "",
|
|
description: booking.description || "",
|
|
startTime: booking.startTime ? dayjs(booking.startTime).format() : "",
|
|
endTime: booking.endTime ? dayjs(booking.endTime).format() : "",
|
|
organizer: {
|
|
email: organizer.email,
|
|
name: organizer.name ?? "Nameless",
|
|
timeZone: organizer.timeZone,
|
|
language: { translate: tOrganizer, locale: organizer.locale ?? "en" },
|
|
},
|
|
attendees: attendeesList,
|
|
uid: booking.uid,
|
|
recurringEvent: parseRecurringEvent(booking.eventType?.recurringEvent),
|
|
location,
|
|
destinationCalendar: booking?.destinationCalendar || booking?.user?.destinationCalendar,
|
|
};
|
|
|
|
const eventManager = new EventManager(ctx.user);
|
|
|
|
const updatedResult = await eventManager.updateLocation(evt, booking);
|
|
const results = updatedResult.results;
|
|
if (results.length > 0 && results.every((res) => !res.success)) {
|
|
const error = {
|
|
errorCode: "BookingUpdateLocationFailed",
|
|
message: "Updating location failed",
|
|
};
|
|
logger.error(`Booking ${ctx.user.username} failed`, error, results);
|
|
} else {
|
|
await ctx.prisma.booking.update({
|
|
where: {
|
|
id: bookingId,
|
|
},
|
|
data: {
|
|
location,
|
|
references: {
|
|
create: updatedResult.referencesToCreate,
|
|
},
|
|
},
|
|
});
|
|
|
|
const metadata: AdditionalInformation = {};
|
|
if (results.length) {
|
|
metadata.hangoutLink = results[0].updatedEvent?.hangoutLink;
|
|
metadata.conferenceData = results[0].updatedEvent?.conferenceData;
|
|
metadata.entryPoints = results[0].updatedEvent?.entryPoints;
|
|
}
|
|
try {
|
|
await sendLocationChangeEmails({ ...evt, additionalInformation: metadata });
|
|
} catch (error) {
|
|
console.log("Error sending LocationChangeEmails");
|
|
}
|
|
}
|
|
} catch {
|
|
throw new TRPCError({ code: "INTERNAL_SERVER_ERROR" });
|
|
}
|
|
return { message: "Location updated" };
|
|
},
|
|
});
|