refactor: apply biome formatting to packages/sms, prisma, emails, lib (#27880)
* refactor: apply biome formatting to small packages + packages/lib Format packages/sms, packages/prisma, packages/platform/libraries, packages/platform/examples, packages/platform/types, packages/emails, and packages/lib. Excludes packages/platform/examples/base/src/pages/[bookingUid].tsx due to pre-existing lint errors. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * revert: remove packages/platform formatting changes Revert biome formatting for packages/platform as requested. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
1 parent
7aefefc31f
commit
e2119879ae
53 files changed
+301
-385
No files matched your search
@@ -25,21 +25,13 @@ const eventTypeDisableAttendeeEmail = (metadata?: EventTypeMetadata) => {
|
||||
return !!metadata?.disableStandardEmails?.all?.attendee;
|
||||
};
|
||||
|
||||
export const sendOrganizerPaymentRefundFailedEmail = async (
|
||||
calEvent: CalendarEvent
|
||||
) => {
|
||||
export const sendOrganizerPaymentRefundFailedEmail = async (calEvent: CalendarEvent) => {
|
||||
const emailsToSend: Promise<unknown>[] = [];
|
||||
emailsToSend.push(
|
||||
sendEmail(() => new OrganizerPaymentRefundFailedEmail({ calEvent }))
|
||||
);
|
||||
emailsToSend.push(sendEmail(() => new OrganizerPaymentRefundFailedEmail({ calEvent })));
|
||||
|
||||
if (calEvent.team?.members) {
|
||||
for (const teamMember of calEvent.team.members) {
|
||||
emailsToSend.push(
|
||||
sendEmail(
|
||||
() => new OrganizerPaymentRefundFailedEmail({ calEvent, teamMember })
|
||||
)
|
||||
);
|
||||
emailsToSend.push(sendEmail(() => new OrganizerPaymentRefundFailedEmail({ calEvent, teamMember })));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -99,9 +91,7 @@ export const sendCreditBalanceLowWarningEmails = async (input: {
|
||||
}
|
||||
|
||||
if (user) {
|
||||
await sendEmail(
|
||||
() => new CreditBalanceLowWarningEmail({ user, balance, creditFor })
|
||||
);
|
||||
await sendEmail(() => new CreditBalanceLowWarningEmail({ user, balance, creditFor }));
|
||||
}
|
||||
};
|
||||
|
||||
@@ -135,19 +125,14 @@ export const sendCreditBalanceLimitReachedEmails = async ({
|
||||
|
||||
for (const admin of team.adminAndOwners) {
|
||||
emailsToSend.push(
|
||||
sendEmail(
|
||||
() =>
|
||||
new CreditBalanceLimitReachedEmail({ user: admin, team, creditFor })
|
||||
)
|
||||
sendEmail(() => new CreditBalanceLimitReachedEmail({ user: admin, team, creditFor }))
|
||||
);
|
||||
}
|
||||
await Promise.all(emailsToSend);
|
||||
}
|
||||
|
||||
if (user) {
|
||||
await sendEmail(
|
||||
() => new CreditBalanceLimitReachedEmail({ user, creditFor })
|
||||
);
|
||||
await sendEmail(() => new CreditBalanceLimitReachedEmail({ user, creditFor }));
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -9,9 +9,11 @@ import AttendeeScheduledEmail from "./templates/attendee-scheduled-email";
|
||||
const mockGetEmailSettings = vi.fn();
|
||||
|
||||
vi.mock("@calcom/features/organizations/repositories/OrganizationSettingsRepository", () => ({
|
||||
OrganizationSettingsRepository: vi.fn().mockImplementation(function() { return {
|
||||
getEmailSettings: mockGetEmailSettings,
|
||||
}; }),
|
||||
OrganizationSettingsRepository: vi.fn().mockImplementation(function () {
|
||||
return {
|
||||
getEmailSettings: mockGetEmailSettings,
|
||||
};
|
||||
}),
|
||||
}));
|
||||
|
||||
vi.mock("@calcom/prisma", () => ({
|
||||
@@ -184,7 +186,6 @@ describe("shouldSkipAttendeeEmailWithSettings", () => {
|
||||
});
|
||||
|
||||
describe("AttendeeScheduledEmail - Privacy fix for seated events", () => {
|
||||
|
||||
const createMockPerson = (name: string, email: string): Person => ({
|
||||
name,
|
||||
email,
|
||||
|
||||
@@ -15,7 +15,10 @@ const sendEmail = async (prepare: () => BaseEmail) => {
|
||||
return await email.sendEmail();
|
||||
} catch (e) {
|
||||
const errorName = e instanceof Error ? e.name : "UnknownError";
|
||||
console.error(`${email?.constructor?.name ?? "Email"}.sendEmail oauth-email-service failed (${errorName})`, e);
|
||||
console.error(
|
||||
`${email?.constructor?.name ?? "Email"}.sendEmail oauth-email-service failed (${errorName})`,
|
||||
e
|
||||
);
|
||||
throw e;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -72,8 +72,8 @@ export const BaseScheduledEmail = (
|
||||
props.title
|
||||
? props.title
|
||||
: props.calEvent.recurringEvent?.count
|
||||
? "your_event_has_been_scheduled_recurring"
|
||||
: "your_event_has_been_scheduled"
|
||||
? "your_event_has_been_scheduled_recurring"
|
||||
: "your_event_has_been_scheduled"
|
||||
)}
|
||||
callToAction={
|
||||
props.callToAction === null
|
||||
|
||||
@@ -26,15 +26,25 @@ export const CreditBalanceLimitReachedEmail = (
|
||||
|
||||
if (team) {
|
||||
return (
|
||||
<V2BaseEmailHtml subject={user.t("action_required_out_of_credits", { teamName: team.name, interpolation: { escapeValue: false } })}>
|
||||
<V2BaseEmailHtml
|
||||
subject={user.t("action_required_out_of_credits", {
|
||||
teamName: team.name,
|
||||
interpolation: { escapeValue: false },
|
||||
})}>
|
||||
<p style={{ fontWeight: 400, lineHeight: "24px" }}>
|
||||
<> {user.t("hi_user_name", { name: user.name, interpolation: { escapeValue: false } })},</>
|
||||
</p>
|
||||
<p style={{ fontWeight: 400, lineHeight: "24px", marginBottom: "20px" }}>
|
||||
<>
|
||||
{isCalAi
|
||||
? user.t("cal_ai_credit_limit_reached_message", { teamName: team.name, interpolation: { escapeValue: false } })
|
||||
: user.t("credit_limit_reached_message", { teamName: team.name, interpolation: { escapeValue: false } })}
|
||||
? user.t("cal_ai_credit_limit_reached_message", {
|
||||
teamName: team.name,
|
||||
interpolation: { escapeValue: false },
|
||||
})
|
||||
: user.t("credit_limit_reached_message", {
|
||||
teamName: team.name,
|
||||
interpolation: { escapeValue: false },
|
||||
})}
|
||||
</>
|
||||
</p>
|
||||
<div style={{ textAlign: "center", marginTop: "24px" }}>
|
||||
|
||||
@@ -27,15 +27,25 @@ export const CreditBalanceLowWarningEmail = (
|
||||
|
||||
if (team) {
|
||||
return (
|
||||
<V2BaseEmailHtml subject={user.t("team_credits_low_warning", { teamName: team.name, interpolation: { escapeValue: false } })}>
|
||||
<V2BaseEmailHtml
|
||||
subject={user.t("team_credits_low_warning", {
|
||||
teamName: team.name,
|
||||
interpolation: { escapeValue: false },
|
||||
})}>
|
||||
<p style={{ fontWeight: 400, lineHeight: "24px" }}>
|
||||
<> {user.t("hi_user_name", { name: user.name, interpolation: { escapeValue: false } })},</>
|
||||
</p>
|
||||
<p style={{ fontWeight: 400, lineHeight: "24px", marginBottom: "20px" }}>
|
||||
<>
|
||||
{isCalAi
|
||||
? user.t("cal_ai_low_credits_warning_message", { teamName: team.name, interpolation: { escapeValue: false } })
|
||||
: user.t("low_credits_warning_message", { teamName: team.name, interpolation: { escapeValue: false } })}
|
||||
? user.t("cal_ai_low_credits_warning_message", {
|
||||
teamName: team.name,
|
||||
interpolation: { escapeValue: false },
|
||||
})
|
||||
: user.t("low_credits_warning_message", {
|
||||
teamName: team.name,
|
||||
interpolation: { escapeValue: false },
|
||||
})}
|
||||
</>
|
||||
</p>
|
||||
<p
|
||||
|
||||
@@ -35,9 +35,7 @@ export const OAuthClientApprovedNotificationEmail = ({
|
||||
}}>
|
||||
{language("oauth_client_approved_email_title", { clientName })}
|
||||
</p>
|
||||
<p style={{ fontWeight: 400 }}>
|
||||
{language("hi_user", { name: userName || language("there") })}!
|
||||
</p>
|
||||
<p style={{ fontWeight: 400 }}>{language("hi_user", { name: userName || language("there") })}!</p>
|
||||
<p style={{ fontWeight: 400, lineHeight: "24px" }}>{language("oauth_client_approved_email_body")}</p>
|
||||
<table
|
||||
role="presentation"
|
||||
|
||||
@@ -7,8 +7,10 @@ export const OrganizerCancelledEmail = (props: React.ComponentProps<typeof Organ
|
||||
const title = "event_request_cancelled";
|
||||
const subject = "event_cancelled_subject";
|
||||
const isRoundRobin = props.calEvent.schedulingType === SchedulingType.ROUND_ROBIN;
|
||||
const subtitle = props.reassigned
|
||||
? (isRoundRobin ? t("event_reassigned_subtitle") : t("event_reassigned_subtitle_generic"))
|
||||
const subtitle = props.reassigned
|
||||
? isRoundRobin
|
||||
? t("event_reassigned_subtitle")
|
||||
: t("event_reassigned_subtitle_generic")
|
||||
: "";
|
||||
return (
|
||||
<OrganizerScheduledEmail
|
||||
|
||||
@@ -55,7 +55,10 @@ export const OrganizerScheduledEmail = (
|
||||
) : (
|
||||
<>
|
||||
{props.attendeeCancelled
|
||||
? t("attendee_no_longer_attending_subtitle", { name: props.attendee.name, interpolation: { escapeValue: false } })
|
||||
? t("attendee_no_longer_attending_subtitle", {
|
||||
name: props.attendee.name,
|
||||
interpolation: { escapeValue: false },
|
||||
})
|
||||
: ""}
|
||||
</>
|
||||
)
|
||||
|
||||
@@ -136,7 +136,7 @@ export const TeamInviteEmail = (
|
||||
parentTeamName: props.parentTeamName,
|
||||
prevLink: props.prevLink,
|
||||
newLink: props.newLink,
|
||||
orgName: props.parentTeamName ?? props.isOrg ? props.teamName : "",
|
||||
orgName: (props.parentTeamName ?? props.isOrg) ? props.teamName : "",
|
||||
prevLinkWithoutProtocol: props.prevLink?.replace(/https?:\/\//, ""),
|
||||
newLinkWithoutProtocol: props.newLink?.replace(/https?:\/\//, ""),
|
||||
};
|
||||
|
||||
@@ -45,10 +45,10 @@ export default class AttendeeWasRequestedToRescheduleEmail extends OrganizerSche
|
||||
<p style="color: #494949;">${this.t("when")}</p>
|
||||
<p style="color: #494949; font-weight: 400; line-height: 24px;text-decoration: line-through;">
|
||||
${this.t(this.getOrganizerStart("dddd").toLowerCase())}, ${this.t(
|
||||
this.getOrganizerStart("MMMM").toLowerCase()
|
||||
)} ${this.getOrganizerStart("D")}, ${this.getOrganizerStart("YYYY")} | ${this.getOrganizerStart(
|
||||
"h:mma"
|
||||
)} - ${this.getOrganizerEnd("h:mma")} <span style="color: #888888">(${this.getTimezone()})</span>
|
||||
this.getOrganizerStart("MMMM").toLowerCase()
|
||||
)} ${this.getOrganizerStart("D")}, ${this.getOrganizerStart("YYYY")} | ${this.getOrganizerStart(
|
||||
"h:mma"
|
||||
)} - ${this.getOrganizerEnd("h:mma")} <span style="color: #888888">(${this.getTimezone()})</span>
|
||||
</p>
|
||||
</div>`;
|
||||
}
|
||||
|
||||
@@ -53,7 +53,7 @@ export default class DisabledAppEmail extends BaseEmail {
|
||||
return this.appType.some((type) => type === "payment")
|
||||
? this.t("disable_payment_app", { appName: this.appName, title: this.title })
|
||||
: this.appType.some((type) => type === "video")
|
||||
? this.t("app_disabled_video", { appName: this.appName })
|
||||
: this.t("app_disabled", { appName: this.appName });
|
||||
? this.t("app_disabled_video", { appName: this.appName })
|
||||
: this.t("app_disabled", { appName: this.appName });
|
||||
}
|
||||
}
|
||||
@@ -94,8 +94,8 @@ ${this.t(
|
||||
title
|
||||
? title
|
||||
: this.calEvent.recurringEvent?.count
|
||||
? "new_event_scheduled_recurring"
|
||||
: "new_event_scheduled"
|
||||
? "new_event_scheduled_recurring"
|
||||
: "new_event_scheduled"
|
||||
)}
|
||||
${this.t(subtitle)}
|
||||
${extraInfo}
|
||||
|
||||
@@ -467,7 +467,7 @@ type RichDescriptionCalEvent = {
|
||||
additionalInformation?: AdditionalInformation;
|
||||
location?: string | null;
|
||||
uid?: string | null;
|
||||
}
|
||||
};
|
||||
|
||||
export const getRichDescriptionHTML = (
|
||||
calEvent: RichDescriptionCalEvent,
|
||||
@@ -649,5 +649,5 @@ export const getVideoCallUrlFromCalEvent = (calEvent: {
|
||||
};
|
||||
|
||||
export const getVideoCallPassword = (videoCallData?: VideoCallData): string => {
|
||||
return isDailyVideoCall(videoCallData) ? "" : videoCallData?.password ?? "";
|
||||
return isDailyVideoCall(videoCallData) ? "" : (videoCallData?.password ?? "");
|
||||
};
|
||||
@@ -243,8 +243,8 @@ export default abstract class BaseCalendarService implements Calendar {
|
||||
throw new Error(`Error creating iCalString:=> ${error?.message} : ${error?.name} `);
|
||||
|
||||
const mainHostDestinationCalendar = event.destinationCalendar
|
||||
? event.destinationCalendar.find((cal) => cal.credentialId === credentialId) ??
|
||||
event.destinationCalendar[0]
|
||||
? (event.destinationCalendar.find((cal) => cal.credentialId === credentialId) ??
|
||||
event.destinationCalendar[0])
|
||||
: undefined;
|
||||
|
||||
// We create the event directly on iCal
|
||||
|
||||
@@ -1,6 +1,11 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
|
||||
import { getOGImageVersion, constructMeetingImage, constructAppImage, constructGenericImage } from "./OgImages";
|
||||
import {
|
||||
getOGImageVersion,
|
||||
constructMeetingImage,
|
||||
constructAppImage,
|
||||
constructGenericImage,
|
||||
} from "./OgImages";
|
||||
|
||||
describe("OgImages", () => {
|
||||
describe("getOGImageVersion", () => {
|
||||
|
||||
+21
-72
@@ -47,8 +47,7 @@ const joinMultipleNames = (names: string[] = []) => {
|
||||
return `${names.length > 0 ? `${names.join(", ")} & ${lastName}` : lastName}`;
|
||||
};
|
||||
|
||||
const makeAbsoluteUrl = (url: string) =>
|
||||
/^https?:\/\//.test(url) ? url : `${CAL_URL}${url}`;
|
||||
const makeAbsoluteUrl = (url: string) => (/^https?:\/\//.test(url) ? url : `${CAL_URL}${url}`);
|
||||
|
||||
const OG_ASSETS = {
|
||||
meeting: {
|
||||
@@ -82,20 +81,12 @@ export const getOGImageVersion = async (
|
||||
...(additionalInputs ?? {}),
|
||||
};
|
||||
|
||||
const content = JSON.stringify(
|
||||
versionInputs,
|
||||
Object.keys(versionInputs).sort()
|
||||
);
|
||||
const content = JSON.stringify(versionInputs, Object.keys(versionInputs).sort());
|
||||
|
||||
// Use Web Crypto API instead of Node.js crypto for Edge Runtime compatibility (`/api/social/og/image` is an Edge Runtime route)
|
||||
const hashBuffer = await crypto.subtle.digest(
|
||||
"SHA-256",
|
||||
new TextEncoder().encode(content)
|
||||
);
|
||||
const hashBuffer = await crypto.subtle.digest("SHA-256", new TextEncoder().encode(content));
|
||||
const hashArray = Array.from(new Uint8Array(hashBuffer));
|
||||
const hashHex = hashArray
|
||||
.map((b) => b.toString(16).padStart(2, "0"))
|
||||
.join("");
|
||||
const hashHex = hashArray.map((b) => b.toString(16).padStart(2, "0")).join("");
|
||||
|
||||
return hashHex.substring(0, 8);
|
||||
};
|
||||
@@ -156,10 +147,7 @@ export const constructAppImage = async ({
|
||||
return encodeURIComponent(`/api/social/og/image?${params.toString()}`);
|
||||
};
|
||||
|
||||
export const constructGenericImage = async ({
|
||||
title,
|
||||
description,
|
||||
}: GenericImageProps): Promise<string> => {
|
||||
export const constructGenericImage = async ({ title, description }: GenericImageProps): Promise<string> => {
|
||||
const params = new URLSearchParams({
|
||||
type: "generic",
|
||||
title,
|
||||
@@ -171,11 +159,7 @@ export const constructGenericImage = async ({
|
||||
return encodeURIComponent(`/api/social/og/image?${params.toString()}`);
|
||||
};
|
||||
|
||||
const Wrapper = ({
|
||||
children,
|
||||
variant = "light",
|
||||
rotateBackground,
|
||||
}: WrapperProps) => (
|
||||
const Wrapper = ({ children, variant = "light", rotateBackground }: WrapperProps) => (
|
||||
<div tw="flex w-full h-full">
|
||||
<img
|
||||
tw="flex absolute left-0 top-0 w-full h-[110%]"
|
||||
@@ -185,9 +169,7 @@ const Wrapper = ({
|
||||
width="1200"
|
||||
height="600"
|
||||
/>
|
||||
<div tw="flex flex-col w-full h-full px-[80px] py-[70px] items-start justify-center">
|
||||
{children}
|
||||
</div>
|
||||
<div tw="flex flex-col w-full h-full px-[80px] py-[70px] items-start justify-center">{children}</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -204,8 +186,7 @@ export const Meeting = ({ title, users = [], profile }: MeetingImageProps) => {
|
||||
// any non existing images for dynamic collectives, while at the same time removing them from
|
||||
// the names list, because the profile name of that event is a concatenation of all names.
|
||||
const attendees = (profile.image ? [profile, ...users] : users).filter(
|
||||
(value, index, self) =>
|
||||
self.findIndex((v) => v.name === value.name) === index
|
||||
(value, index, self) => self.findIndex((v) => v.name === value.name) === index
|
||||
);
|
||||
|
||||
// Construct list of avatar urls, removes duplicates and empty profile images
|
||||
@@ -218,26 +199,15 @@ export const Meeting = ({ title, users = [], profile }: MeetingImageProps) => {
|
||||
|
||||
// In case there is NO other attendee than the single meeting profile without an image, we add
|
||||
// that name back in here, since the event probably is a round robin event.
|
||||
const names =
|
||||
attendees.length > 0 ? attendees.map((user) => user.name) : [profile.name];
|
||||
const names = attendees.length > 0 ? attendees.map((user) => user.name) : [profile.name];
|
||||
|
||||
return (
|
||||
<Wrapper variant={config.variant}>
|
||||
<div tw="h-full flex flex-col justify-start">
|
||||
<div
|
||||
tw="flex items-center justify-center"
|
||||
style={{ fontFamily: "cal", fontWeight: 300 }}
|
||||
>
|
||||
<img
|
||||
src={`${WEBAPP_URL}/${config.logo}`}
|
||||
width={config.logoWidth}
|
||||
alt="Logo"
|
||||
/>
|
||||
<div tw="flex items-center justify-center" style={{ fontFamily: "cal", fontWeight: 300 }}>
|
||||
<img src={`${WEBAPP_URL}/${config.logo}`} width={config.logoWidth} alt="Logo" />
|
||||
{avatars.length > 0 && (
|
||||
<div
|
||||
style={{ color: "#111827" }}
|
||||
tw="font-bold text-[92px] mx-8 bottom-2"
|
||||
>
|
||||
<div style={{ color: "#111827" }} tw="font-bold text-[92px] mx-8 bottom-2">
|
||||
/
|
||||
</div>
|
||||
)}
|
||||
@@ -254,27 +224,20 @@ export const Meeting = ({ title, users = [], profile }: MeetingImageProps) => {
|
||||
))}
|
||||
{avatars.length > 3 && (
|
||||
<div
|
||||
tw={`flex items-center justify-center w-[${config.avatarSize}px] h-[${config.avatarSize}px] rounded-full bg-black text-inverted text-[54px] font-bold`}
|
||||
>
|
||||
<span tw="flex top-[-5px] left-[-5px]">
|
||||
+{avatars.length - 3}
|
||||
</span>
|
||||
tw={`flex items-center justify-center w-[${config.avatarSize}px] h-[${config.avatarSize}px] rounded-full bg-black text-inverted text-[54px] font-bold`}>
|
||||
<span tw="flex top-[-5px] left-[-5px]">+{avatars.length - 3}</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
style={{ color: "#111827" }}
|
||||
tw="relative flex text-[54px] w-full flex-col mt-auto"
|
||||
>
|
||||
<div style={{ color: "#111827" }} tw="relative flex text-[54px] w-full flex-col mt-auto">
|
||||
<div
|
||||
tw="flex w-[1040px] overflow-hidden"
|
||||
style={{
|
||||
whiteSpace: "nowrap",
|
||||
fontFamily: "cal",
|
||||
textOverflow: "ellipsis",
|
||||
}}
|
||||
>
|
||||
}}>
|
||||
Meet {joinMultipleNames(names)}
|
||||
</div>
|
||||
<div
|
||||
@@ -283,8 +246,7 @@ export const Meeting = ({ title, users = [], profile }: MeetingImageProps) => {
|
||||
whiteSpace: "nowrap",
|
||||
fontFamily: "inter",
|
||||
textOverflow: "ellipsis",
|
||||
}}
|
||||
>
|
||||
}}>
|
||||
{title}
|
||||
</div>
|
||||
</div>
|
||||
@@ -355,10 +317,7 @@ export const App = ({ name, description, logoUrl }: AppImageProps) => {
|
||||
</div>
|
||||
</div>
|
||||
<div style={{ color: "#111827" }} tw="flex mt-auto w-full flex-col">
|
||||
<div
|
||||
tw="flex text-[64px] mb-7"
|
||||
style={{ fontFamily: "cal", fontWeight: 600 }}
|
||||
>
|
||||
<div tw="flex text-[64px] mb-7" style={{ fontFamily: "cal", fontWeight: 600 }}>
|
||||
{name}
|
||||
</div>
|
||||
<div tw="flex text-[36px]" style={{ fontFamily: "inter" }}>
|
||||
@@ -380,21 +339,11 @@ export const Generic = ({ title, description }: GenericImageProps) => {
|
||||
return (
|
||||
<Wrapper variant={config.variant}>
|
||||
<div tw="h-full flex flex-col justify-start">
|
||||
<div
|
||||
tw="flex items-center justify-center"
|
||||
style={{ fontFamily: "cal", fontWeight: 300 }}
|
||||
>
|
||||
<img
|
||||
src={`${WEBAPP_URL}/${config.logo}`}
|
||||
width={config.logoWidth}
|
||||
alt="Logo"
|
||||
/>
|
||||
<div tw="flex items-center justify-center" style={{ fontFamily: "cal", fontWeight: 300 }}>
|
||||
<img src={`${WEBAPP_URL}/${config.logo}`} width={config.logoWidth} alt="Logo" />
|
||||
</div>
|
||||
|
||||
<div
|
||||
style={{ color: "#111827" }}
|
||||
tw="relative flex text-[54px] w-full flex-col mt-auto"
|
||||
>
|
||||
<div style={{ color: "#111827" }} tw="relative flex text-[54px] w-full flex-col mt-auto">
|
||||
<div tw="flex w-[1040px]" style={{ fontFamily: "cal" }}>
|
||||
{title}
|
||||
</div>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { AppOnboardingSteps } from "@calcom/lib/apps/appOnboardingSteps"
|
||||
import type { AppOnboardingSteps } from "@calcom/lib/apps/appOnboardingSteps";
|
||||
|
||||
// biome-ignore lint/style/useNodejsImportProtocol: Vite env
|
||||
import { stringify } from "querystring";
|
||||
|
||||
@@ -19,10 +19,13 @@ const computeInstallCountsFromDB = async (): Promise<Record<string, number>> =>
|
||||
"installCount" DESC
|
||||
`
|
||||
);
|
||||
return mostPopularApps.reduce((acc, { appId, installCount }) => {
|
||||
acc[appId] = installCount;
|
||||
return acc;
|
||||
}, {} as Record<string, number>);
|
||||
return mostPopularApps.reduce(
|
||||
(acc, { appId, installCount }) => {
|
||||
acc[appId] = installCount;
|
||||
return acc;
|
||||
},
|
||||
{} as Record<string, number>
|
||||
);
|
||||
};
|
||||
|
||||
const getInstallCountPerApp = async (): Promise<Record<string, number>> => {
|
||||
|
||||
@@ -28,7 +28,7 @@ export function getAvailabilityFromSchedule(schedule: Schedule): Availability[]
|
||||
days: [day],
|
||||
startTime: time.start,
|
||||
endTime: time.end,
|
||||
} as Availability);
|
||||
}) as Availability;
|
||||
|
||||
const filteredTimes = times.filter((time) => {
|
||||
let idx: number;
|
||||
|
||||
@@ -109,8 +109,8 @@ export const buildCalEventFromBooking = async ({
|
||||
destinationCalendar: booking.destinationCalendar
|
||||
? [booking.destinationCalendar]
|
||||
: booking.user?.destinationCalendar
|
||||
? [booking.user?.destinationCalendar]
|
||||
: [],
|
||||
? [booking.user?.destinationCalendar]
|
||||
: [],
|
||||
seatsPerTimeSlot: booking.eventType?.seatsPerTimeSlot,
|
||||
seatsShowAttendees: booking.eventType?.seatsShowAttendees,
|
||||
customReplyToEmail: booking.eventType?.customReplyToEmail,
|
||||
|
||||
+43
-108
@@ -9,23 +9,14 @@ function ensureProtocol(url: string | undefined): string {
|
||||
return `https://${url}`;
|
||||
}
|
||||
|
||||
const VERCEL_URL = process.env.NEXT_PUBLIC_VERCEL_URL
|
||||
? `https://${process.env.NEXT_PUBLIC_VERCEL_URL}`
|
||||
: "";
|
||||
const RAILWAY_STATIC_URL = process.env.RAILWAY_STATIC_URL
|
||||
? `https://${process.env.RAILWAY_STATIC_URL}`
|
||||
: "";
|
||||
const HEROKU_URL = process.env.HEROKU_APP_NAME
|
||||
? `https://${process.env.HEROKU_APP_NAME}.herokuapp.com`
|
||||
: "";
|
||||
const RENDER_URL = process.env.RENDER_EXTERNAL_URL
|
||||
? `https://${process.env.RENDER_EXTERNAL_URL}`
|
||||
: "";
|
||||
const VERCEL_URL = process.env.NEXT_PUBLIC_VERCEL_URL ? `https://${process.env.NEXT_PUBLIC_VERCEL_URL}` : "";
|
||||
const RAILWAY_STATIC_URL = process.env.RAILWAY_STATIC_URL ? `https://${process.env.RAILWAY_STATIC_URL}` : "";
|
||||
const HEROKU_URL = process.env.HEROKU_APP_NAME ? `https://${process.env.HEROKU_APP_NAME}.herokuapp.com` : "";
|
||||
const RENDER_URL = process.env.RENDER_EXTERNAL_URL ? `https://${process.env.RENDER_EXTERNAL_URL}` : "";
|
||||
export const CALCOM_ENV = process.env.CALCOM_ENV || process.env.NODE_ENV;
|
||||
export const IS_PRODUCTION = CALCOM_ENV === "production";
|
||||
export const IS_PRODUCTION_BUILD = process.env.NODE_ENV === "production";
|
||||
export const ORGANIZER_EMAIL_EXEMPT_DOMAINS =
|
||||
process.env.ORGANIZER_EMAIL_EXEMPT_DOMAINS || "";
|
||||
export const ORGANIZER_EMAIL_EXEMPT_DOMAINS = process.env.ORGANIZER_EMAIL_EXEMPT_DOMAINS || "";
|
||||
const IS_DEV = CALCOM_ENV === "development";
|
||||
export const SINGLE_ORG_SLUG = process.env.NEXT_PUBLIC_SINGLE_ORG_SLUG;
|
||||
/** https://app.cal.com */
|
||||
@@ -39,21 +30,16 @@ export const WEBAPP_URL =
|
||||
|
||||
// OAuth needs to have HTTPS(which is not generally setup locally) and a valid tld(*.local isn't a valid tld)
|
||||
// So for development purpose, we would stick to localhost only
|
||||
export const WEBAPP_URL_FOR_OAUTH =
|
||||
IS_PRODUCTION || IS_DEV ? WEBAPP_URL : "http://localhost:3000";
|
||||
export const WEBAPP_URL_FOR_OAUTH = IS_PRODUCTION || IS_DEV ? WEBAPP_URL : "http://localhost:3000";
|
||||
|
||||
/** @deprecated use `WEBAPP_URL` */
|
||||
export const BASE_URL = WEBAPP_URL;
|
||||
export const WEBSITE_URL =
|
||||
ensureProtocol(process.env.NEXT_PUBLIC_WEBSITE_URL) || "https://cal.com";
|
||||
export const WEBSITE_URL = ensureProtocol(process.env.NEXT_PUBLIC_WEBSITE_URL) || "https://cal.com";
|
||||
export const APP_NAME = process.env.NEXT_PUBLIC_APP_NAME || "Cal.com";
|
||||
export const SUPPORT_MAIL_ADDRESS =
|
||||
process.env.NEXT_PUBLIC_SUPPORT_MAIL_ADDRESS || "help@cal.com";
|
||||
export const COMPANY_NAME =
|
||||
process.env.NEXT_PUBLIC_COMPANY_NAME || "Cal.com, Inc.";
|
||||
export const SUPPORT_MAIL_ADDRESS = process.env.NEXT_PUBLIC_SUPPORT_MAIL_ADDRESS || "help@cal.com";
|
||||
export const COMPANY_NAME = process.env.NEXT_PUBLIC_COMPANY_NAME || "Cal.com, Inc.";
|
||||
export const SENDER_ID = process.env.NEXT_PUBLIC_SENDER_ID || "Cal";
|
||||
export const SENDER_NAME =
|
||||
process.env.NEXT_PUBLIC_SENDGRID_SENDER_NAME || "Cal.com";
|
||||
export const SENDER_NAME = process.env.NEXT_PUBLIC_SENDGRID_SENDER_NAME || "Cal.com";
|
||||
export const EMAIL_FROM_NAME = process.env.EMAIL_FROM_NAME || APP_NAME;
|
||||
|
||||
// This is the URL from which all Cal Links and their assets are served.
|
||||
@@ -82,11 +68,8 @@ export const CONSOLE_URL =
|
||||
: `https://console.cal.com`;
|
||||
const CAL_DOMAINS = [".cal.com", ".cal.dev", ".cal.eu", ".cal.qa"];
|
||||
const WEBAPP_HOSTNAME = new URL(WEBAPP_URL).hostname;
|
||||
export const IS_SELF_HOSTED = !CAL_DOMAINS.some((domain) =>
|
||||
WEBAPP_HOSTNAME.endsWith(domain)
|
||||
);
|
||||
export const EMBED_LIB_URL =
|
||||
process.env.NEXT_PUBLIC_EMBED_LIB_URL || `${WEBAPP_URL}/embed/embed.js`;
|
||||
export const IS_SELF_HOSTED = !CAL_DOMAINS.some((domain) => WEBAPP_HOSTNAME.endsWith(domain));
|
||||
export const EMBED_LIB_URL = process.env.NEXT_PUBLIC_EMBED_LIB_URL || `${WEBAPP_URL}/embed/embed.js`;
|
||||
export const TRIAL_LIMIT_DAYS = 14;
|
||||
export const MAX_SEATS_PER_TIME_SLOT = 1000;
|
||||
|
||||
@@ -96,26 +79,16 @@ export const MAX_EVENT_DURATION_MINUTES = 1440;
|
||||
/** Minimum duration allowed for an event in minutes */
|
||||
export const MIN_EVENT_DURATION_MINUTES = 1;
|
||||
|
||||
export const HOSTED_CAL_FEATURES =
|
||||
process.env.NEXT_PUBLIC_HOSTED_CAL_FEATURES || !IS_SELF_HOSTED;
|
||||
export const HOSTED_CAL_FEATURES = process.env.NEXT_PUBLIC_HOSTED_CAL_FEATURES || !IS_SELF_HOSTED;
|
||||
|
||||
export const PUBLIC_QUERY_RESERVATION_INTERVAL_SECONDS =
|
||||
parseInt(
|
||||
process.env.NEXT_PUBLIC_QUERY_RESERVATION_INTERVAL_SECONDS ?? "",
|
||||
10
|
||||
) || 30;
|
||||
parseInt(process.env.NEXT_PUBLIC_QUERY_RESERVATION_INTERVAL_SECONDS ?? "", 10) || 30;
|
||||
|
||||
// Must be lower than PUBLIC_QUERY_RESERVATION_INTERVAL_SECONDS
|
||||
export const PUBLIC_QUERY_RESERVATION_STALE_TIME_SECONDS =
|
||||
parseInt(
|
||||
process.env.NEXT_PUBLIC_QUERY_RESERVATION_STALE_TIME_SECONDS ?? "",
|
||||
10
|
||||
) || 20;
|
||||
parseInt(process.env.NEXT_PUBLIC_QUERY_RESERVATION_STALE_TIME_SECONDS ?? "", 10) || 20;
|
||||
export const PUBLIC_QUERY_AVAILABLE_SLOTS_INTERVAL_SECONDS =
|
||||
parseInt(
|
||||
process.env.NEXT_PUBLIC_QUERY_AVAILABLE_SLOTS_INTERVAL_SECONDS ?? "",
|
||||
10
|
||||
) || 5 * 60;
|
||||
parseInt(process.env.NEXT_PUBLIC_QUERY_AVAILABLE_SLOTS_INTERVAL_SECONDS ?? "", 10) || 5 * 60;
|
||||
export const PUBLIC_INVALIDATE_AVAILABLE_SLOTS_ON_BOOKING_FORM =
|
||||
process.env.NEXT_PUBLIC_INVALIDATE_AVAILABLE_SLOTS_ON_BOOKING_FORM === "1";
|
||||
|
||||
@@ -124,8 +97,7 @@ export const PUBLIC_QUICK_AVAILABILITY_ROLLOUT =
|
||||
parseInt(process.env.NEXT_PUBLIC_QUICK_AVAILABILITY_ROLLOUT ?? "", 10) || 0;
|
||||
|
||||
/** @deprecated use `WEBAPP_URL` */
|
||||
export const NEXT_PUBLIC_BASE_URL =
|
||||
process.env.NEXT_PUBLIC_WEBAPP_URL || `https://${process.env.VERCEL_URL}`;
|
||||
export const NEXT_PUBLIC_BASE_URL = process.env.NEXT_PUBLIC_WEBAPP_URL || `https://${process.env.VERCEL_URL}`;
|
||||
export const LOGO = "/calcom-logo-white-word.svg";
|
||||
export const LOGO_DARK = "/cal-logo-word-black.svg";
|
||||
export const LOGO_ICON = "/cal-com-icon-white.svg";
|
||||
@@ -163,15 +135,10 @@ export const IS_TEAM_BILLING_ENABLED_CLIENT =
|
||||
export const FULL_NAME_LENGTH_MAX_LIMIT = 50;
|
||||
export const API_NAME_LENGTH_MAX_LIMIT = 80;
|
||||
export const MINUTES_TO_BOOK = process.env.NEXT_PUBLIC_MINUTES_TO_BOOK || "5";
|
||||
export const ENABLE_PROFILE_SWITCHER =
|
||||
process.env.NEXT_PUBLIC_ENABLE_PROFILE_SWITCHER === "1";
|
||||
export const ENABLE_PROFILE_SWITCHER = process.env.NEXT_PUBLIC_ENABLE_PROFILE_SWITCHER === "1";
|
||||
// Needed for orgs
|
||||
export const ALLOWED_HOSTNAMES = JSON.parse(
|
||||
`[${process.env.ALLOWED_HOSTNAMES || ""}]`
|
||||
) as string[];
|
||||
export const RESERVED_SUBDOMAINS = JSON.parse(
|
||||
`[${process.env.RESERVED_SUBDOMAINS || ""}]`
|
||||
) as string[];
|
||||
export const ALLOWED_HOSTNAMES = JSON.parse(`[${process.env.ALLOWED_HOSTNAMES || ""}]`) as string[];
|
||||
export const RESERVED_SUBDOMAINS = JSON.parse(`[${process.env.RESERVED_SUBDOMAINS || ""}]`) as string[];
|
||||
|
||||
export const ORGANIZATION_SELF_SERVE_PRICE = parseFloat(
|
||||
process.env.NEXT_PUBLIC_ORGANIZATIONS_SELF_SERVE_PRICE_NEW || "37"
|
||||
@@ -182,19 +149,15 @@ export const IS_MAILHOG_ENABLED = process.env.E2E_TEST_MAILHOG_ENABLED === "1";
|
||||
export const CALCOM_VERSION = process.env.NEXT_PUBLIC_CALCOM_VERSION as string;
|
||||
|
||||
export const APP_CREDENTIAL_SHARING_ENABLED =
|
||||
!!process.env.CALCOM_CREDENTIAL_SYNC_SECRET &&
|
||||
!!process.env.CALCOM_APP_CREDENTIAL_ENCRYPTION_KEY;
|
||||
!!process.env.CALCOM_CREDENTIAL_SYNC_SECRET && !!process.env.CALCOM_APP_CREDENTIAL_ENCRYPTION_KEY;
|
||||
export const CREDENTIAL_SYNC_SECRET = process.env.CALCOM_CREDENTIAL_SYNC_SECRET;
|
||||
export const CREDENTIAL_SYNC_SECRET_HEADER_NAME =
|
||||
process.env.CALCOM_CREDENTIAL_SYNC_HEADER_NAME ||
|
||||
"calcom-credential-sync-secret";
|
||||
process.env.CALCOM_CREDENTIAL_SYNC_HEADER_NAME || "calcom-credential-sync-secret";
|
||||
|
||||
export const CREDENTIAL_SYNC_ENDPOINT =
|
||||
process.env.CALCOM_CREDENTIAL_SYNC_ENDPOINT;
|
||||
export const CREDENTIAL_SYNC_ENDPOINT = process.env.CALCOM_CREDENTIAL_SYNC_ENDPOINT;
|
||||
|
||||
// Service Account Encryption Key for encrypting/decrypting service account keys
|
||||
export const SERVICE_ACCOUNT_ENCRYPTION_KEY =
|
||||
process.env.CALCOM_SERVICE_ACCOUNT_ENCRYPTION_KEY;
|
||||
export const SERVICE_ACCOUNT_ENCRYPTION_KEY = process.env.CALCOM_SERVICE_ACCOUNT_ENCRYPTION_KEY;
|
||||
|
||||
export const DEFAULT_LIGHT_BRAND_COLOR = "#292929";
|
||||
export const DEFAULT_DARK_BRAND_COLOR = "#fafafa";
|
||||
@@ -210,9 +173,7 @@ export const MAX_NB_INVITES = 100;
|
||||
|
||||
export const URL_PROTOCOL_REGEX = /(^\w+:|^)\/\//;
|
||||
|
||||
export const IS_VISUAL_REGRESSION_TESTING = Boolean(
|
||||
globalThis.window?.Meticulous?.isRunningAsTest
|
||||
);
|
||||
export const IS_VISUAL_REGRESSION_TESTING = Boolean(globalThis.window?.Meticulous?.isRunningAsTest);
|
||||
|
||||
export const BOOKER_NUMBER_OF_DAYS_TO_LOAD = parseInt(
|
||||
process.env.NEXT_PUBLIC_BOOKER_NUMBER_OF_DAYS_TO_LOAD ?? "0",
|
||||
@@ -220,20 +181,15 @@ export const BOOKER_NUMBER_OF_DAYS_TO_LOAD = parseInt(
|
||||
);
|
||||
|
||||
export const CLOUDFLARE_SITE_ID = process.env.NEXT_PUBLIC_CLOUDFLARE_SITEKEY;
|
||||
export const CLOUDFLARE_USE_TURNSTILE_IN_BOOKER =
|
||||
process.env.NEXT_PUBLIC_CLOUDFLARE_USE_TURNSTILE_IN_BOOKER;
|
||||
export const ORG_SELF_SERVE_ENABLED =
|
||||
process.env.NEXT_PUBLIC_ORG_SELF_SERVE_ENABLED === "1";
|
||||
export const CLOUDFLARE_USE_TURNSTILE_IN_BOOKER = process.env.NEXT_PUBLIC_CLOUDFLARE_USE_TURNSTILE_IN_BOOKER;
|
||||
export const ORG_SELF_SERVE_ENABLED = process.env.NEXT_PUBLIC_ORG_SELF_SERVE_ENABLED === "1";
|
||||
export const ORG_MINIMUM_PUBLISHED_TEAMS_SELF_SERVE = 0;
|
||||
export const ORG_MINIMUM_PUBLISHED_TEAMS_SELF_SERVE_HELPER_DIALOGUE = 1;
|
||||
|
||||
export const CALCOM_PRIVATE_API_ROUTE =
|
||||
process.env.CALCOM_PRIVATE_API_ROUTE || "https://goblin.cal.com";
|
||||
export const CALCOM_PRIVATE_API_ROUTE = process.env.CALCOM_PRIVATE_API_ROUTE || "https://goblin.cal.com";
|
||||
export const WEBSITE_PRIVACY_POLICY_URL =
|
||||
process.env.NEXT_PUBLIC_WEBSITE_PRIVACY_POLICY_URL ||
|
||||
"https://cal.com/privacy";
|
||||
export const WEBSITE_TERMS_URL =
|
||||
process.env.NEXT_PUBLIC_WEBSITE_TERMS_URL || "https://cal.com/terms";
|
||||
process.env.NEXT_PUBLIC_WEBSITE_PRIVACY_POLICY_URL || "https://cal.com/privacy";
|
||||
export const WEBSITE_TERMS_URL = process.env.NEXT_PUBLIC_WEBSITE_TERMS_URL || "https://cal.com/terms";
|
||||
export const LINGO_DOT_DEV_API_KEY = process.env.LINGO_DOT_DEV_API_KEY;
|
||||
|
||||
/**
|
||||
@@ -258,55 +214,36 @@ export const RECORDING_IN_PROGRESS_ICON = IS_PRODUCTION
|
||||
? `${WEBAPP_URL}/stop-recording.svg`
|
||||
: `https://app.cal.com/stop-recording.svg`;
|
||||
|
||||
export const SCOPE_USERINFO_PROFILE =
|
||||
"https://www.googleapis.com/auth/userinfo.profile";
|
||||
export const SCOPE_USERINFO_EMAIL =
|
||||
"https://www.googleapis.com/auth/userinfo.email";
|
||||
export const GOOGLE_OAUTH_SCOPES = [
|
||||
SCOPE_USERINFO_PROFILE,
|
||||
SCOPE_USERINFO_EMAIL,
|
||||
];
|
||||
export const SCOPE_USERINFO_PROFILE = "https://www.googleapis.com/auth/userinfo.profile";
|
||||
export const SCOPE_USERINFO_EMAIL = "https://www.googleapis.com/auth/userinfo.email";
|
||||
export const GOOGLE_OAUTH_SCOPES = [SCOPE_USERINFO_PROFILE, SCOPE_USERINFO_EMAIL];
|
||||
export const GOOGLE_CALENDAR_SCOPES = [
|
||||
"https://www.googleapis.com/auth/calendar.events",
|
||||
"https://www.googleapis.com/auth/calendar.readonly",
|
||||
];
|
||||
export const DIRECTORY_IDS_TO_LOG =
|
||||
process.env.DIRECTORY_IDS_TO_LOG?.split(",") || [];
|
||||
export const SCANNING_WORKFLOW_STEPS = !!(
|
||||
!IS_SELF_HOSTED && process.env.IFFY_API_KEY
|
||||
);
|
||||
export const DIRECTORY_IDS_TO_LOG = process.env.DIRECTORY_IDS_TO_LOG?.split(",") || [];
|
||||
export const SCANNING_WORKFLOW_STEPS = !!(!IS_SELF_HOSTED && process.env.IFFY_API_KEY);
|
||||
|
||||
// Cloudflare URL Scanner - checks URLs for malicious content in workflows and event types
|
||||
export const URL_SCANNING_ENABLED =
|
||||
!!process.env.CLOUDFLARE_URL_SCANNER_API_TOKEN &&
|
||||
!!process.env.CLOUDFLARE_ACCOUNT_ID;
|
||||
!!process.env.CLOUDFLARE_URL_SCANNER_API_TOKEN && !!process.env.CLOUDFLARE_ACCOUNT_ID;
|
||||
|
||||
export const IS_DUB_REFERRALS_ENABLED =
|
||||
!!process.env.NEXT_PUBLIC_DUB_PROGRAM_ID &&
|
||||
process.env.NEXT_PUBLIC_DUB_PROGRAM_ID !== "";
|
||||
!!process.env.NEXT_PUBLIC_DUB_PROGRAM_ID && process.env.NEXT_PUBLIC_DUB_PROGRAM_ID !== "";
|
||||
|
||||
export const CAL_VIDEO_MEETING_LINK_FOR_TESTING =
|
||||
process.env.CAL_VIDEO_MEETING_LINK_FOR_TESTING;
|
||||
export const CAL_VIDEO_MEETING_LINK_FOR_TESTING = process.env.CAL_VIDEO_MEETING_LINK_FOR_TESTING;
|
||||
|
||||
export const IS_SMS_CREDITS_ENABLED =
|
||||
!!process.env.NEXT_PUBLIC_STRIPE_CREDITS_PRICE_ID ||
|
||||
!!process.env.NEXT_PUBLIC_IS_E2E;
|
||||
export const DATABASE_CHUNK_SIZE = parseInt(
|
||||
process.env.DATABASE_CHUNK_SIZE || "25",
|
||||
10
|
||||
);
|
||||
!!process.env.NEXT_PUBLIC_STRIPE_CREDITS_PRICE_ID || !!process.env.NEXT_PUBLIC_IS_E2E;
|
||||
export const DATABASE_CHUNK_SIZE = parseInt(process.env.DATABASE_CHUNK_SIZE || "25", 10);
|
||||
|
||||
export const NEXTJS_CACHE_TTL = 3600; // 1 hour
|
||||
|
||||
export const DEFAULT_GROUP_ID = "default_group_id";
|
||||
|
||||
const _rawCalAiPrice =
|
||||
process.env.NEXT_PUBLIC_CAL_AI_PHONE_NUMBER_MONTHLY_PRICE;
|
||||
const _rawCalAiPrice = process.env.NEXT_PUBLIC_CAL_AI_PHONE_NUMBER_MONTHLY_PRICE;
|
||||
export const CAL_AI_PHONE_NUMBER_MONTHLY_PRICE = (() => {
|
||||
const parsed =
|
||||
_rawCalAiPrice && _rawCalAiPrice.trim() !== ""
|
||||
? Number(_rawCalAiPrice)
|
||||
: NaN;
|
||||
const parsed = _rawCalAiPrice && _rawCalAiPrice.trim() !== "" ? Number(_rawCalAiPrice) : NaN;
|
||||
return Number.isFinite(parsed) ? parsed : 5;
|
||||
})();
|
||||
|
||||
@@ -338,6 +275,4 @@ export const ORG_TRIAL_DAYS = process.env.STRIPE_ORG_TRIAL_DAYS
|
||||
export const IS_API_V2_E2E = process.env.IS_E2E === "true";
|
||||
|
||||
export const ENABLE_ASYNC_TASKER =
|
||||
process.env.ENABLE_ASYNC_TASKER === "true" &&
|
||||
!process.env.NEXT_PUBLIC_IS_E2E &&
|
||||
!IS_API_V2_E2E;
|
||||
process.env.ENABLE_ASYNC_TASKER === "true" && !process.env.NEXT_PUBLIC_IS_E2E && !IS_API_V2_E2E;
|
||||
@@ -29,7 +29,9 @@ export const objectsToCsv = (data: Record<string, any>[]): string => {
|
||||
// Create CSV rows
|
||||
const csvRows = [
|
||||
// Header row
|
||||
headers.map((header) => sanitizeValue(header)).join(","),
|
||||
headers
|
||||
.map((header) => sanitizeValue(header))
|
||||
.join(","),
|
||||
// Data rows
|
||||
...data.map((row) =>
|
||||
headers
|
||||
|
||||
@@ -81,7 +81,7 @@ function handleDomainCreationError(error: {
|
||||
domain?: string | null;
|
||||
message?: string | null;
|
||||
invalidToken?: boolean | null;
|
||||
}){
|
||||
}) {
|
||||
// Vercel returns "forbidden" for various permission issues, not just domain ownership
|
||||
if (error.code === "forbidden") {
|
||||
const errorMessage =
|
||||
@@ -130,7 +130,7 @@ function handleDomainDeletionError(error: {
|
||||
domain?: string | null;
|
||||
message?: string | null;
|
||||
invalidToken?: boolean | null;
|
||||
}){
|
||||
}) {
|
||||
if (error.code === "not_found") {
|
||||
// Domain is already deleted
|
||||
return true;
|
||||
|
||||
@@ -26,18 +26,18 @@ export class ErrorWithCode extends Error {
|
||||
|
||||
/**
|
||||
* Converts unknown error types to Error objects.
|
||||
*
|
||||
*
|
||||
* @deprecated For server-side code, use `getServerErrorFromUnknown` from `@calcom/lib/server/getServerErrorFromUnknown` instead.
|
||||
* This function should only be used in client-side or isomorphic code (React components, shared utilities).
|
||||
*
|
||||
*
|
||||
* Use this function when:
|
||||
* - You're in a React component that runs on the client
|
||||
* - You're in shared/isomorphic code that cannot import server-only dependencies
|
||||
* - You only need a basic Error object without HTTP status code mapping
|
||||
*
|
||||
*
|
||||
* For server-side error handling (API routes, tRPC handlers, webhooks), use `getServerErrorFromUnknown`
|
||||
* which provides proper HTTP status code mapping, error redaction, and handles Zod/Prisma/Stripe errors.
|
||||
*
|
||||
*
|
||||
* @param cause - The unknown error to convert
|
||||
* @returns An Error object with optional statusCode and code properties
|
||||
*/
|
||||
|
||||
@@ -92,7 +92,7 @@ export const getBookingWithResponses = <
|
||||
select: BookingSelect & {
|
||||
responses: true;
|
||||
};
|
||||
}>
|
||||
}>,
|
||||
>(
|
||||
booking: T
|
||||
) => {
|
||||
|
||||
@@ -48,4 +48,3 @@ export function isSafeUrlToLoadResourceFrom(urlString: string) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -139,7 +139,7 @@ export function filterActiveLinks<
|
||||
expiresAt?: Date | null;
|
||||
maxUsageCount?: number | null;
|
||||
usageCount?: number | null;
|
||||
}
|
||||
},
|
||||
>(links: T[], timezone?: string | null): T[] {
|
||||
return links.filter((link) => !isLinkExpired(link, timezone));
|
||||
}
|
||||
@@ -93,4 +93,3 @@ export function getGoogleCalendarClient(): GoogleCalendarClient {
|
||||
}
|
||||
return defaultClient;
|
||||
}
|
||||
|
||||
@@ -2,7 +2,11 @@ import dayjs from "@calcom/dayjs";
|
||||
import { HolidayRepository } from "@calcom/features/holidays/repositories/HolidayRepository";
|
||||
|
||||
import { GOOGLE_HOLIDAY_CALENDARS, HOLIDAY_CACHE_DAYS } from "./constants";
|
||||
import { getGoogleCalendarClient, type GoogleCalendarClient, type GoogleCalendarHoliday } from "./GoogleCalendarClient";
|
||||
import {
|
||||
getGoogleCalendarClient,
|
||||
type GoogleCalendarClient,
|
||||
type GoogleCalendarHoliday,
|
||||
} from "./GoogleCalendarClient";
|
||||
|
||||
export interface CachedHoliday {
|
||||
id: string;
|
||||
@@ -67,14 +71,16 @@ export class HolidayServiceCachingProxy {
|
||||
|
||||
const cached = await HolidayRepository.findManyCachedHolidays({ countryCode, year });
|
||||
|
||||
return cached.map((h): CachedHoliday => ({
|
||||
id: h.id,
|
||||
countryCode: h.countryCode,
|
||||
eventId: h.eventId,
|
||||
name: h.name,
|
||||
date: h.date,
|
||||
year: h.year,
|
||||
}));
|
||||
return cached.map(
|
||||
(h): CachedHoliday => ({
|
||||
id: h.id,
|
||||
countryCode: h.countryCode,
|
||||
eventId: h.eventId,
|
||||
name: h.name,
|
||||
date: h.date,
|
||||
year: h.year,
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
async getHolidaysInRange(countryCode: string, startDate: Date, endDate: Date): Promise<CachedHoliday[]> {
|
||||
@@ -94,14 +100,16 @@ export class HolidayServiceCachingProxy {
|
||||
endDate,
|
||||
});
|
||||
|
||||
return cached.map((h): CachedHoliday => ({
|
||||
id: h.id,
|
||||
countryCode: h.countryCode,
|
||||
eventId: h.eventId,
|
||||
name: h.name,
|
||||
date: h.date,
|
||||
year: h.year,
|
||||
}));
|
||||
return cached.map(
|
||||
(h): CachedHoliday => ({
|
||||
id: h.id,
|
||||
countryCode: h.countryCode,
|
||||
eventId: h.eventId,
|
||||
name: h.name,
|
||||
date: h.date,
|
||||
year: h.year,
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -113,4 +121,3 @@ export function getHolidayServiceCachingProxy(): HolidayServiceCachingProxy {
|
||||
}
|
||||
return defaultProxy;
|
||||
}
|
||||
|
||||
@@ -13,7 +13,10 @@ export const GOOGLE_RELIGIOUS_HOLIDAY_CALENDARS: Record<string, { name: string;
|
||||
HINDU: { name: "Hindu Holidays", calendarId: "en.hinduism#holiday@group.v.calendar.google.com" },
|
||||
JEWISH: { name: "Jewish Holidays", calendarId: "en.judaism#holiday@group.v.calendar.google.com" },
|
||||
MUSLIM: { name: "Muslim Holidays", calendarId: "en.islamic#holiday@group.v.calendar.google.com" },
|
||||
ORTHODOX: { name: "Orthodox Holidays", calendarId: "en.orthodox_christianity#holiday@group.v.calendar.google.com" },
|
||||
ORTHODOX: {
|
||||
name: "Orthodox Holidays",
|
||||
calendarId: "en.orthodox_christianity#holiday@group.v.calendar.google.com",
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -90,7 +93,10 @@ export const GOOGLE_HOLIDAY_CALENDARS: Record<string, { name: string; calendarId
|
||||
EE: { name: "Estonia", calendarId: "en.ee.official#holiday@group.v.calendar.google.com" },
|
||||
SZ: { name: "Eswatini", calendarId: "en.sz.official#holiday@group.v.calendar.google.com" },
|
||||
ET: { name: "Ethiopia", calendarId: "en.et.official#holiday@group.v.calendar.google.com" },
|
||||
FK: { name: "Falkland Islands (Islas Malvinas)", calendarId: "en.fk.official#holiday@group.v.calendar.google.com" },
|
||||
FK: {
|
||||
name: "Falkland Islands (Islas Malvinas)",
|
||||
calendarId: "en.fk.official#holiday@group.v.calendar.google.com",
|
||||
},
|
||||
FO: { name: "Faroe Islands", calendarId: "en.fo.official#holiday@group.v.calendar.google.com" },
|
||||
FJ: { name: "Fiji", calendarId: "en.fj.official#holiday@group.v.calendar.google.com" },
|
||||
FI: { name: "Finland", calendarId: "en.finnish.official#holiday@group.v.calendar.google.com" },
|
||||
@@ -257,4 +263,3 @@ export const GOOGLE_HOLIDAY_CALENDARS: Record<string, { name: string; calendarId
|
||||
ZM: { name: "Zambia", calendarId: "en.zm.official#holiday@group.v.calendar.google.com" },
|
||||
ZW: { name: "Zimbabwe", calendarId: "en.zw.official#holiday@group.v.calendar.google.com" },
|
||||
};
|
||||
|
||||
@@ -31,7 +31,10 @@ const HOLIDAY_EMOJI_MAP: Array<{ keywords: string[]; emoji: string }> = [
|
||||
{ keywords: ["revolution", "revolución"], emoji: "✊" },
|
||||
{ keywords: ["unity", "unification", "einheit", "german unity"], emoji: "🤝" },
|
||||
{ keywords: ["flag day", "día de la bandera"], emoji: "🚩" },
|
||||
{ keywords: ["labor", "labour", "workers", "trabajo", "travail", "may day", "tag der arbeit"], emoji: "👷" },
|
||||
{
|
||||
keywords: ["labor", "labour", "workers", "trabajo", "travail", "may day", "tag der arbeit"],
|
||||
emoji: "👷",
|
||||
},
|
||||
{ keywords: ["diwali", "deepavali", "dipavali"], emoji: "🪔" },
|
||||
{ keywords: ["holi"], emoji: "🎨" },
|
||||
{ keywords: ["dussehra", "dasara", "vijayadashami"], emoji: "🏹" },
|
||||
|
||||
@@ -5,7 +5,9 @@ import { describe, expect, it } from "vitest";
|
||||
import { useCompatSearchParams } from "./useCompatSearchParams";
|
||||
|
||||
vi.mock("next/navigation", () => ({
|
||||
ReadonlyURLSearchParams: vi.fn().mockImplementation(function(a) { return a; }),
|
||||
ReadonlyURLSearchParams: vi.fn().mockImplementation(function (a) {
|
||||
return a;
|
||||
}),
|
||||
}));
|
||||
|
||||
describe("useCompatSearchParams hook", () => {
|
||||
|
||||
@@ -21,4 +21,7 @@ function _getPeriodStartDatesBetween(
|
||||
return dates;
|
||||
}
|
||||
|
||||
export const getPeriodStartDatesBetween = withReporting(_getPeriodStartDatesBetween, "getPeriodStartDatesBetween");
|
||||
export const getPeriodStartDatesBetween = withReporting(
|
||||
_getPeriodStartDatesBetween,
|
||||
"getPeriodStartDatesBetween"
|
||||
);
|
||||
@@ -1,4 +1,4 @@
|
||||
import fs from "node:fs"
|
||||
import fs from "node:fs";
|
||||
|
||||
import logger from "./logger";
|
||||
|
||||
|
||||
@@ -6,4 +6,4 @@ export function caseInsensitive<T extends string | string[]>(
|
||||
? stringOrStringArray.map((string) => string.toLowerCase())
|
||||
: stringOrStringArray.toLowerCase()
|
||||
) as T extends string[] ? string[] : string;
|
||||
}
|
||||
}
|
||||
@@ -38,7 +38,7 @@ export function rateLimiter() {
|
||||
log.warn("Disabled because the UNKEY_ROOT_KEY environment variable was not found.");
|
||||
warned = true;
|
||||
}
|
||||
return () => ({ success: true, limit: 10, remaining: 999, reset: 0 } as RatelimitResponse);
|
||||
return () => ({ success: true, limit: 10, remaining: 999, reset: 0 }) as RatelimitResponse;
|
||||
}
|
||||
const timeout = {
|
||||
fallback: { success: true, limit: 10, remaining: 999, reset: 0 },
|
||||
|
||||
@@ -30,37 +30,40 @@ export function transformDateOverridesForAtom(
|
||||
schedule: { availability: ScheduleOverride },
|
||||
timeZone: string
|
||||
) {
|
||||
const acc = schedule.availability.reduce((acc, override) => {
|
||||
// only if future date override
|
||||
const currentUtcOffset = dayjs().tz(timeZone).utcOffset();
|
||||
const currentTimeInTz = dayjs().utc().add(currentUtcOffset, "minute");
|
||||
const acc = schedule.availability.reduce(
|
||||
(acc, override) => {
|
||||
// only if future date override
|
||||
const currentUtcOffset = dayjs().tz(timeZone).utcOffset();
|
||||
const currentTimeInTz = dayjs().utc().add(currentUtcOffset, "minute");
|
||||
|
||||
if (!override.date || dayjs(override.date).isBefore(currentTimeInTz, "day")) {
|
||||
if (!override.date || dayjs(override.date).isBefore(currentTimeInTz, "day")) {
|
||||
return acc;
|
||||
}
|
||||
const newValue = {
|
||||
start: dayjs
|
||||
.utc(override.date)
|
||||
.hour(override.startTime.getUTCHours())
|
||||
.minute(override.startTime.getUTCMinutes())
|
||||
.toDate(),
|
||||
end: dayjs
|
||||
.utc(override.date)
|
||||
.hour(override.endTime.getUTCHours())
|
||||
.minute(override.endTime.getUTCMinutes())
|
||||
.toDate(),
|
||||
};
|
||||
const dayRangeIndex = acc.findIndex(
|
||||
// early return prevents override.date from ever being empty.
|
||||
(item) => override.date && yyyymmdd(item.ranges[0].start) === yyyymmdd(override.date)
|
||||
);
|
||||
if (dayRangeIndex === -1) {
|
||||
acc.push({ ranges: [newValue] });
|
||||
return acc;
|
||||
}
|
||||
acc[dayRangeIndex].ranges.push(newValue);
|
||||
return acc;
|
||||
}
|
||||
const newValue = {
|
||||
start: dayjs
|
||||
.utc(override.date)
|
||||
.hour(override.startTime.getUTCHours())
|
||||
.minute(override.startTime.getUTCMinutes())
|
||||
.toDate(),
|
||||
end: dayjs
|
||||
.utc(override.date)
|
||||
.hour(override.endTime.getUTCHours())
|
||||
.minute(override.endTime.getUTCMinutes())
|
||||
.toDate(),
|
||||
};
|
||||
const dayRangeIndex = acc.findIndex(
|
||||
// early return prevents override.date from ever being empty.
|
||||
(item) => override.date && yyyymmdd(item.ranges[0].start) === yyyymmdd(override.date)
|
||||
);
|
||||
if (dayRangeIndex === -1) {
|
||||
acc.push({ ranges: [newValue] });
|
||||
return acc;
|
||||
}
|
||||
acc[dayRangeIndex].ranges.push(newValue);
|
||||
return acc;
|
||||
}, [] as { ranges: TimeRange[] }[]);
|
||||
},
|
||||
[] as { ranges: TimeRange[] }[]
|
||||
);
|
||||
|
||||
acc.sort((a, b) => {
|
||||
const aTime = a.ranges?.[0]?.start?.getTime?.() ?? 0;
|
||||
|
||||
@@ -138,9 +138,10 @@ const noOpSpan: TelemetrySpan = {
|
||||
* Creates a console-logging span for local development when Sentry is not configured.
|
||||
* Tracks attributes and logs them with duration when the span completes.
|
||||
*/
|
||||
function createConsoleSpan(
|
||||
options: TelemetrySpanOptions
|
||||
): { span: TelemetrySpan; logCompletion: (durationMs: number, error?: unknown) => void } {
|
||||
function createConsoleSpan(options: TelemetrySpanOptions): {
|
||||
span: TelemetrySpan;
|
||||
logCompletion: (durationMs: number, error?: unknown) => void;
|
||||
} {
|
||||
const attributes: Record<string, string | number | boolean> = { ...options.attributes };
|
||||
|
||||
const span: TelemetrySpan = {
|
||||
|
||||
@@ -2,7 +2,6 @@ import { describe, it, expect } from "vitest";
|
||||
import { hashEmail, Md5PiiHasher } from "./PiiHasher";
|
||||
|
||||
describe("PII Hasher Test Suite", () => {
|
||||
|
||||
const hasher = new Md5PiiHasher("test-salt");
|
||||
|
||||
it("can hash email addresses deterministically and preserve domain", async () => {
|
||||
|
||||
@@ -22,7 +22,10 @@ function isZodError(cause: unknown): cause is ZodError {
|
||||
function isPrismaError(cause: unknown): cause is Prisma.PrismaClientKnownRequestError {
|
||||
return (
|
||||
cause instanceof Prisma.PrismaClientKnownRequestError ||
|
||||
(cause instanceof Error && "code" in cause && typeof cause.code === "string" && cause.code.startsWith("P"))
|
||||
(cause instanceof Error &&
|
||||
"code" in cause &&
|
||||
typeof cause.code === "string" &&
|
||||
cause.code.startsWith("P"))
|
||||
);
|
||||
}
|
||||
|
||||
@@ -32,8 +35,8 @@ function parseZodErrorIssues(issues: ZodIssue[]): string {
|
||||
i.code === "invalid_union"
|
||||
? i.unionErrors.map((ue) => parseZodErrorIssues(ue.issues)).join("; ")
|
||||
: i.code === "unrecognized_keys"
|
||||
? i.message
|
||||
: `${i.path.length ? `${i.code} in '${i.path}': ` : ""}${i.message}`
|
||||
? i.message
|
||||
: `${i.path.length ? `${i.code} in '${i.path}': ` : ""}${i.message}`
|
||||
)
|
||||
.join("; ");
|
||||
}
|
||||
|
||||
@@ -13,7 +13,7 @@ export function withSelectedCalendars<
|
||||
},
|
||||
TUser extends {
|
||||
selectedCalendars: TCalendar[];
|
||||
}
|
||||
},
|
||||
>(user: UserWithLegacySelectedCalendars<TCalendar, TUser>): UserWithSelectedCalendars<TCalendar, TUser> {
|
||||
// We are renaming selectedCalendars to allSelectedCalendars to make it clear that it contains all the calendars including eventType calendars
|
||||
const { selectedCalendars, ...restUser } = user;
|
||||
|
||||
@@ -81,16 +81,12 @@ describe("validateUrlForSSRFSync", () => {
|
||||
});
|
||||
|
||||
it("allows /api/avatar/{uuid}.png only", () => {
|
||||
expect(validateUrlForSSRFSync("/api/avatar/ba0fa3a6-2aac-4032-8230-3789f5752e5a.png").isValid).toBe(
|
||||
true
|
||||
);
|
||||
expect(validateUrlForSSRFSync("/api/avatar/ba0fa3a6-2aac-4032-8230-3789f5752e5a.png").isValid).toBe(true);
|
||||
expect(validateUrlForSSRFSync("/api/avatar/any-value.png").isValid).toBe(true);
|
||||
});
|
||||
|
||||
it("rejects /api/avatar/ path without .png extension", () => {
|
||||
expect(validateUrlForSSRFSync("/api/avatar/ba0fa3a6-2aac-4032-8230-3789f5752e5a").isValid).toBe(
|
||||
false
|
||||
);
|
||||
expect(validateUrlForSSRFSync("/api/avatar/ba0fa3a6-2aac-4032-8230-3789f5752e5a").isValid).toBe(false);
|
||||
expect(validateUrlForSSRFSync("/api/avatar/foo.jpg").isValid).toBe(false);
|
||||
});
|
||||
|
||||
@@ -213,4 +209,4 @@ describe("Self-hosted environment behavior", () => {
|
||||
expect(validateSelfHosted("ftp://internal-server/file").isValid).toBe(false);
|
||||
expect(validateSelfHosted("javascript:alert(1)").isValid).toBe(false);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -7,9 +7,7 @@ import { redactError } from "../redactError";
|
||||
import type { ILogger } from "./types";
|
||||
|
||||
const isAsyncTaskerEnabled =
|
||||
ENABLE_ASYNC_TASKER &&
|
||||
process.env.TRIGGER_SECRET_KEY &&
|
||||
process.env.TRIGGER_API_URL;
|
||||
ENABLE_ASYNC_TASKER && process.env.TRIGGER_SECRET_KEY && process.env.TRIGGER_API_URL;
|
||||
|
||||
export abstract class Tasker<T> {
|
||||
protected readonly asyncTasker: T;
|
||||
@@ -24,10 +22,7 @@ export abstract class Tasker<T> {
|
||||
this.logger = dependencies.logger;
|
||||
|
||||
if (!isAsyncTaskerEnabled) {
|
||||
if (
|
||||
ENABLE_ASYNC_TASKER &&
|
||||
(!process.env.TRIGGER_SECRET_KEY || !process.env.TRIGGER_API_URL)
|
||||
) {
|
||||
if (ENABLE_ASYNC_TASKER && (!process.env.TRIGGER_SECRET_KEY || !process.env.TRIGGER_API_URL)) {
|
||||
this.logger.info(
|
||||
"Missing env variables TRIGGER_SECRET_KEY or TRIGGER_API_URL, falling back to Sync tasker."
|
||||
);
|
||||
@@ -41,18 +36,14 @@ export abstract class Tasker<T> {
|
||||
});
|
||||
}
|
||||
|
||||
this.asyncTasker = isAsyncTaskerEnabled
|
||||
? dependencies.asyncTasker
|
||||
: dependencies.syncTasker;
|
||||
this.asyncTasker = isAsyncTaskerEnabled ? dependencies.asyncTasker : dependencies.syncTasker;
|
||||
this.syncTasker = dependencies.syncTasker;
|
||||
}
|
||||
|
||||
public async dispatch<K extends keyof T>(
|
||||
taskName: K,
|
||||
...args: T[K] extends (...args: any[]) => any ? Parameters<T[K]> : never
|
||||
): Promise<
|
||||
T[K] extends (...args: any[]) => any ? Awaited<ReturnType<T[K]>> : never
|
||||
> {
|
||||
): Promise<T[K] extends (...args: any[]) => any ? Awaited<ReturnType<T[K]>> : never> {
|
||||
this.logger.info(`Safely Dispatching task '${String(taskName)}'`, { args });
|
||||
return this._safeDispatch(taskName, ...args);
|
||||
}
|
||||
@@ -60,14 +51,10 @@ export abstract class Tasker<T> {
|
||||
private async _safeDispatch<K extends keyof T>(
|
||||
taskName: K,
|
||||
...args: T[K] extends (...args: any[]) => any ? Parameters<T[K]> : never
|
||||
): Promise<
|
||||
T[K] extends (...args: any[]) => any ? Awaited<ReturnType<T[K]>> : never
|
||||
> {
|
||||
): Promise<T[K] extends (...args: any[]) => any ? Awaited<ReturnType<T[K]>> : never> {
|
||||
try {
|
||||
this.logger.info(
|
||||
`${isAsyncTaskerEnabled ? "AsyncTasker" : "SyncTasker"} '${String(
|
||||
taskName
|
||||
)}' dispatched.`
|
||||
`${isAsyncTaskerEnabled ? "AsyncTasker" : "SyncTasker"} '${String(taskName)}' dispatched.`
|
||||
);
|
||||
const method = this.asyncTasker[taskName] as (...args: any[]) => any;
|
||||
return await method.apply(this.asyncTasker, args);
|
||||
@@ -85,20 +72,13 @@ export abstract class Tasker<T> {
|
||||
throw err;
|
||||
}
|
||||
|
||||
this.logger.warn(
|
||||
`Trying again with SyncTasker for '${String(taskName)}'.`
|
||||
);
|
||||
this.logger.warn(`Trying again with SyncTasker for '${String(taskName)}'.`);
|
||||
|
||||
try {
|
||||
const fallbackMethod = this.syncTasker[taskName] as (
|
||||
...args: any[]
|
||||
) => any;
|
||||
const fallbackMethod = this.syncTasker[taskName] as (...args: any[]) => any;
|
||||
return await fallbackMethod.apply(this.syncTasker, args);
|
||||
} catch (err) {
|
||||
this.logger.error(
|
||||
`SyncTasker failed for '${String(taskName)}'.`,
|
||||
this.getErrorDetails(err)
|
||||
);
|
||||
this.logger.error(`SyncTasker failed for '${String(taskName)}'.`, this.getErrorDetails(err));
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -269,7 +269,13 @@ describe("DistributedTracing", () => {
|
||||
tracing.getTracingLogger(context);
|
||||
|
||||
expect(mockLogger.getSubLogger).toHaveBeenCalledWith({
|
||||
prefix: ["distributed-trace", "trace:trace_123", "span:span_456", "op:test_operation", "userId:user_789"],
|
||||
prefix: [
|
||||
"distributed-trace",
|
||||
"trace:trace_123",
|
||||
"span:span_456",
|
||||
"op:test_operation",
|
||||
"userId:user_789",
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
@@ -287,7 +293,13 @@ describe("DistributedTracing", () => {
|
||||
tracing.getTracingLogger(context);
|
||||
|
||||
expect(mockLogger.getSubLogger).toHaveBeenCalledWith({
|
||||
prefix: ["distributed-trace", "trace:trace_123", "span:span_456", "op:test_operation", "userId:user_789"],
|
||||
prefix: [
|
||||
"distributed-trace",
|
||||
"trace:trace_123",
|
||||
"span:span_456",
|
||||
"op:test_operation",
|
||||
"userId:user_789",
|
||||
],
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -14,7 +14,10 @@ export interface IdGenerator {
|
||||
}
|
||||
|
||||
export class DistributedTracing {
|
||||
constructor(private idGenerator: IdGenerator, private loggerInstance: Logger<unknown>) {}
|
||||
constructor(
|
||||
private idGenerator: IdGenerator,
|
||||
private loggerInstance: Logger<unknown>
|
||||
) {}
|
||||
|
||||
createTrace(operation: string, context?: Partial<TraceContext>): TraceContext {
|
||||
const { traceId, spanId, meta, ...otherContext } = context || {};
|
||||
|
||||
@@ -4,4 +4,3 @@ export {
|
||||
type GoogleAdsTrackingData,
|
||||
type LinkedInAdsTrackingData,
|
||||
} from "./server";
|
||||
|
||||
@@ -1,12 +1,8 @@
|
||||
import { createHmac } from "node:crypto";
|
||||
|
||||
// 262992 minutes is 6 months
|
||||
export function generateVideoToken(
|
||||
recordingId: string,
|
||||
expiresInMinutes = 262992
|
||||
) {
|
||||
const secret =
|
||||
process.env.CAL_VIDEO_RECORDING_TOKEN_SECRET || "default-secret-change-me";
|
||||
export function generateVideoToken(recordingId: string, expiresInMinutes = 262992) {
|
||||
const secret = process.env.CAL_VIDEO_RECORDING_TOKEN_SECRET || "default-secret-change-me";
|
||||
const expires = Date.now() + expiresInMinutes * 60 * 1000;
|
||||
|
||||
const payload = `${recordingId}:${expires}`;
|
||||
@@ -21,9 +17,7 @@ export function verifyVideoToken(token: string): {
|
||||
} {
|
||||
try {
|
||||
const [recordingId, expires, receivedHmac] = token.split(":");
|
||||
const secret =
|
||||
process.env.CAL_VIDEO_RECORDING_TOKEN_SECRET ||
|
||||
"default-secret-change-me";
|
||||
const secret = process.env.CAL_VIDEO_RECORDING_TOKEN_SECRET || "default-secret-change-me";
|
||||
|
||||
if (Date.now() > parseInt(expires, 10)) {
|
||||
return { valid: false };
|
||||
@@ -31,9 +25,7 @@ export function verifyVideoToken(token: string): {
|
||||
|
||||
// Verify HMAC
|
||||
const payload = `${recordingId}:${expires}`;
|
||||
const expectedHmac = createHmac("sha256", secret)
|
||||
.update(payload)
|
||||
.digest("hex");
|
||||
const expectedHmac = createHmac("sha256", secret).update(payload).digest("hex");
|
||||
|
||||
if (receivedHmac !== expectedHmac) {
|
||||
return { valid: false };
|
||||
|
||||
@@ -12,7 +12,6 @@ const validateSsrfUrl = (url: string | null | undefined): boolean => {
|
||||
return validateUrlForSSRFSync(url).isValid;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Zod schema for validating user-provided URLs before server-side fetching
|
||||
* Applies synchronous SSRF checks only (no DNS resolution)
|
||||
|
||||
@@ -63,7 +63,6 @@ describe("excludeOrRequireEmailSchema", () => {
|
||||
});
|
||||
|
||||
describe("invalid inputs", () => {
|
||||
|
||||
it("rejects invalid TLD lengths", () => {
|
||||
expect(parse("example.c").success).toBe(false);
|
||||
});
|
||||
|
||||
@@ -357,9 +357,14 @@ export const stringOrNumber = z.union([
|
||||
|
||||
export const requiredCustomInputSchema = z.union([
|
||||
// string must be given & nonempty
|
||||
z.string().trim().min(1),
|
||||
z
|
||||
.string()
|
||||
.trim()
|
||||
.min(1),
|
||||
// boolean must be true if set.
|
||||
z.boolean().refine((v) => v === true),
|
||||
z
|
||||
.boolean()
|
||||
.refine((v) => v === true),
|
||||
]);
|
||||
|
||||
const PlatformClientParamsSchema = z.object({
|
||||
@@ -643,7 +648,7 @@ export function denullishShape<
|
||||
UnknownKeys extends UnknownKeysParam = "strip",
|
||||
Catchall extends ZodTypeAny = ZodTypeAny,
|
||||
Output = objectOutputType<T, Catchall>,
|
||||
Input = objectInputType<T, Catchall>
|
||||
Input = objectInputType<T, Catchall>,
|
||||
>(
|
||||
obj: ZodObject<T, UnknownKeys, Catchall, Output, Input>
|
||||
): ZodObject<ZodDenullishShape<T>, UnknownKeys, Catchall> {
|
||||
@@ -675,13 +680,14 @@ export const entries = <O extends Record<string, unknown>>(
|
||||
/**
|
||||
* Returns a type with all readonly notations removed (traverses recursively on an object)
|
||||
*/
|
||||
type DeepWriteable<T> = T extends Readonly<{
|
||||
-readonly [K in keyof T]: T[K];
|
||||
}>
|
||||
? {
|
||||
-readonly [K in keyof T]: DeepWriteable<T[K]>;
|
||||
}
|
||||
: T; /* Make it work with readonly types (this is not strictly necessary) */
|
||||
type DeepWriteable<T> =
|
||||
T extends Readonly<{
|
||||
-readonly [K in keyof T]: T[K];
|
||||
}>
|
||||
? {
|
||||
-readonly [K in keyof T]: DeepWriteable<T[K]>;
|
||||
}
|
||||
: T; /* Make it work with readonly types (this is not strictly necessary) */
|
||||
|
||||
type FromEntries<T> = T extends [infer Keys, unknown][]
|
||||
? { [K in Keys & PropertyKey]: Extract<T[number], [K, unknown]>[1] }
|
||||
@@ -694,7 +700,7 @@ type FromEntries<T> = T extends [infer Keys, unknown][]
|
||||
* @see https://github.com/3x071c/lsg-remix/blob/e2a9592ba3ec5103556f2cf307c32f08aeaee32d/app/lib/util/fromEntries.ts
|
||||
*/
|
||||
export const fromEntries = <
|
||||
E extends [PropertyKey, unknown][] | ReadonlyArray<readonly [PropertyKey, unknown]>
|
||||
E extends [PropertyKey, unknown][] | ReadonlyArray<readonly [PropertyKey, unknown]>,
|
||||
>(
|
||||
entries: E
|
||||
): FromEntries<DeepWriteable<E>> => {
|
||||
|
||||
@@ -15,7 +15,7 @@ export default class EventCancelledSMS extends SMSManager {
|
||||
const messageText = `${t("hey_there")} ${attendee.name}, ${t("event_request_cancelled")}\n\n${t(
|
||||
"event_cancelled_subject",
|
||||
{
|
||||
title: typeof this.calEvent.title === 'string' ? this.calEvent.title : 'Untitled Event',
|
||||
title: typeof this.calEvent.title === "string" ? this.calEvent.title : "Untitled Event",
|
||||
date: this.getFormattedDate(attendee.timeZone, attendee.language.locale),
|
||||
interpolation: { escapeValue: false },
|
||||
}
|
||||
|
||||
@@ -33,8 +33,8 @@ const handleSendingSMS = async ({
|
||||
identifier: teamId
|
||||
? `handleSendingSMS:team-${teamId}`
|
||||
: organizerUserId
|
||||
? `handleSendingSMS:org-user-${organizerUserId}`
|
||||
: `handleSendingSMS:user-${piiHasher.hash(reminderPhone)}`,
|
||||
? `handleSendingSMS:org-user-${organizerUserId}`
|
||||
: `handleSendingSMS:user-${piiHasher.hash(reminderPhone)}`,
|
||||
rateLimitingType: "sms",
|
||||
});
|
||||
|
||||
|
||||
Reference in new issue
Block a user