chore: standarize rate limit structure (#25736)
* feat: add toggle to opt out of booking title translation in instant meetings (#25547) * feat: add toggle to opt out of booking title translation in instant meetings Co-Authored-By: joe@cal.com <j.auyeung419@gmail.com> * fix: add autoTranslateTitleEnabled to test builder Co-Authored-By: joe@cal.com <j.auyeung419@gmail.com> * fix: respect autoTranslateTitleEnabled toggle in InstantBookingCreateService Co-Authored-By: joe@cal.com <j.auyeung419@gmail.com> * fix: fetch autoTranslateTitleEnabled directly in InstantBookingCreateService Co-Authored-By: joe@cal.com <j.auyeung419@gmail.com> * refactor: rename autoTranslateTitleEnabled to autoTranslateInstantMeetingTitleEnabled - Renamed field to clarify it only applies to instant meeting title translation - Moved Prisma query to EventTypeRepository.findInstantMeetingConfigById() - Removed title translation logic from update.handler.ts (flag only controls instant meeting title) - Updated all references across the codebase - Added new i18n translation keys for the renamed field Co-Authored-By: joe@cal.com <j.auyeung419@gmail.com> * fix: add autoTranslateInstantMeetingTitleEnabled to test destructuring patterns Co-Authored-By: joe@cal.com <j.auyeung419@gmail.com> * fix: use Prisma.TeamCreateInput type for metadata in test helper Co-Authored-By: joe@cal.com <j.auyeung419@gmail.com> * fix: add autoTranslateInstantMeetingTitleEnabled to mockUpdatedEventType in test Co-Authored-By: joe@cal.com <j.auyeung419@gmail.com> * refactor: use generic findByIdMinimal instead of business-specific method - Add autoTranslateInstantMeetingTitleEnabled to eventTypeSelect constant - Remove findInstantMeetingConfigById from EventTypeRepository - Update InstantBookingCreateService to use findByIdMinimal Co-Authored-By: joe@cal.com <j.auyeung419@gmail.com> * refactor: remove autoTranslateInstantMeetingTitleEnabled from eventTypeSelect (not needed for findByIdMinimal) Co-Authored-By: joe@cal.com <j.auyeung419@gmail.com> * feat: add autoTranslateInstantMeetingTitleEnabled to event type form defaults - Rename shouldTranslateTitle to shouldAutoTranslateInstantMeetingTitle for clarity - Add autoTranslateInstantMeetingTitleEnabled to findById and findByIdForOrgAdmin selects - Add autoTranslateInstantMeetingTitleEnabled to useEventTypeForm defaultValues Co-Authored-By: joe@cal.com <j.auyeung419@gmail.com> * fix: only update autoTranslateInstantMeetingTitleEnabled when explicitly provided Fixes issue where omitting the field in update requests would overwrite the saved opt-out setting with the default value (true). Now the field is only included in the update payload when explicitly provided. Co-Authored-By: joe@cal.com <j.auyeung419@gmail.com> --------- Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> * chore: Create standard for rate limits --------- Co-authored-by: Joe Au-Yeung <65426560+joeauyeung@users.noreply.github.com> Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
This commit is contained in:
co-authored by
joe@cal.com
Devin AI
Joe Au-Yeung
parent
cb13874574
commit
d9ebf26e21
@@ -29,7 +29,7 @@ async function handler(req: NextRequest) {
|
||||
|
||||
await checkRateLimitAndThrowError({
|
||||
rateLimitingType: "core",
|
||||
identifier: piiHasher.hash(ip),
|
||||
identifier: `forgotPassword:${piiHasher.hash(ip)}`,
|
||||
});
|
||||
|
||||
try {
|
||||
|
||||
@@ -36,7 +36,7 @@ async function handler(req: NextApiRequest & { userId?: number; traceContext: Tr
|
||||
|
||||
await checkRateLimitAndThrowError({
|
||||
rateLimitingType: "core",
|
||||
identifier: piiHasher.hash(userIp),
|
||||
identifier: `createBooking:${piiHasher.hash(userIp)}`,
|
||||
});
|
||||
|
||||
const session = await getServerSession({ req });
|
||||
|
||||
@@ -38,7 +38,7 @@ async function handler(req: NextApiRequest & RequestMeta) {
|
||||
|
||||
await checkRateLimitAndThrowError({
|
||||
rateLimitingType: "core",
|
||||
identifier: piiHasher.hash(userIp),
|
||||
identifier: `createRecurringBooking:${piiHasher.hash(userIp)}`,
|
||||
});
|
||||
const session = await getServerSession({ req });
|
||||
/* To mimic API behavior and comply with types */
|
||||
|
||||
@@ -56,7 +56,7 @@ export const sendEmailVerification = async ({
|
||||
|
||||
await checkRateLimitAndThrowError({
|
||||
rateLimitingType: "core",
|
||||
identifier: hashEmail(email),
|
||||
identifier: `sendEmailVerification:${hashEmail(email)}`,
|
||||
});
|
||||
|
||||
await prisma.verificationToken.create({
|
||||
|
||||
@@ -917,7 +917,7 @@ describe("RetellAIService", () => {
|
||||
|
||||
expect(vi.mocked(checkRateLimitAndThrowError)).toHaveBeenCalledWith({
|
||||
rateLimitingType: "core",
|
||||
identifier: "test-call:1",
|
||||
identifier: "createTestCall:1",
|
||||
});
|
||||
expect(result).toEqual({
|
||||
callId: "call-123",
|
||||
|
||||
@@ -97,7 +97,7 @@ export class CallService {
|
||||
|
||||
await checkRateLimitAndThrowError({
|
||||
rateLimitingType: "core",
|
||||
identifier: `test-call:${userId}`,
|
||||
identifier: `createTestCall:${userId}`,
|
||||
});
|
||||
|
||||
const toNumber = phoneNumber?.trim();
|
||||
@@ -192,7 +192,7 @@ export class CallService {
|
||||
|
||||
await checkRateLimitAndThrowError({
|
||||
rateLimitingType: "core",
|
||||
identifier: `web-call:${userId}`,
|
||||
identifier: `createWebCall:${userId}`,
|
||||
});
|
||||
|
||||
const agent = await this.deps.agentRepository.findByIdWithCallAccess({
|
||||
|
||||
+2
-2
@@ -207,7 +207,7 @@ describe("CallService", () => {
|
||||
|
||||
expect(checkRateLimitAndThrowError).toHaveBeenCalledWith({
|
||||
rateLimitingType: "core",
|
||||
identifier: "test-call:1",
|
||||
identifier: "createTestCall:1",
|
||||
});
|
||||
});
|
||||
|
||||
@@ -224,7 +224,7 @@ describe("CallService", () => {
|
||||
|
||||
expect(checkRateLimitAndThrowError).toHaveBeenCalledWith({
|
||||
rateLimitingType: "core",
|
||||
identifier: "test-call:42",
|
||||
identifier: "createTestCall:42",
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -400,7 +400,7 @@ const scheduleAIPhoneCallTask = async (args: ScheduleAIPhoneCallTaskArgs) => {
|
||||
if (userId) {
|
||||
await checkRateLimitAndThrowError({
|
||||
rateLimitingType: "core",
|
||||
identifier: `ai-phone-call:${userId}`,
|
||||
identifier: `executeAIPhoneCall:${userId}`,
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -174,9 +174,9 @@ export async function executeAIPhoneCall(payload: string) {
|
||||
}
|
||||
|
||||
const rateLimitIdentifier = data.teamId
|
||||
? `ai-phone-call:team:${data.teamId}`
|
||||
? `executeAIPhoneCall:team-${data.teamId}`
|
||||
: data.userId
|
||||
? `ai-phone-call:user:${data.userId}`
|
||||
? `executeAIPhoneCall:user-${data.userId}`
|
||||
: null;
|
||||
|
||||
if (!rateLimitIdentifier) {
|
||||
|
||||
@@ -31,10 +31,10 @@ const handleSendingSMS = async ({
|
||||
// If neither is provided(Just in case), we check the rate limit for the reminderPhone.
|
||||
await checkSMSRateLimit({
|
||||
identifier: teamId
|
||||
? `handleSendingSMS:team:${teamId}`
|
||||
? `handleSendingSMS:team-${teamId}`
|
||||
: organizerUserId
|
||||
? `handleSendingSMS:user:${organizerUserId}`
|
||||
: `handleSendingSMS:user:${piiHasher.hash(reminderPhone)}`,
|
||||
? `handleSendingSMS:org-user-${organizerUserId}`
|
||||
: `handleSendingSMS:user-${piiHasher.hash(reminderPhone)}`,
|
||||
rateLimitingType: "sms",
|
||||
});
|
||||
|
||||
|
||||
@@ -141,7 +141,7 @@ describe("SMSManager", () => {
|
||||
const result = await smsManager.sendSMSToAttendee(mockCalEvent.attendees[0], "test-booking-uid");
|
||||
|
||||
expect(checkSMSRateLimit).toHaveBeenCalledWith({
|
||||
identifier: "handleSendingSMS:user:1",
|
||||
identifier: "handleSendingSMS:org-user-1",
|
||||
rateLimitingType: "sms",
|
||||
});
|
||||
|
||||
|
||||
@@ -22,7 +22,7 @@ export const addSecondaryEmailHandler = async ({ ctx, input }: AddSecondaryEmail
|
||||
|
||||
await checkRateLimitAndThrowError({
|
||||
rateLimitingType: "core",
|
||||
identifier: `addSecondaryEmail.${user.id}`,
|
||||
identifier: `addSecondaryEmail:${user.id}`,
|
||||
});
|
||||
|
||||
const existingPrimaryEmail = await prisma.user.findUnique({
|
||||
|
||||
@@ -24,7 +24,7 @@ export const resendVerifyEmail = async ({ input, ctx }: ResendEmailOptions) => {
|
||||
|
||||
await checkRateLimitAndThrowError({
|
||||
rateLimitingType: "core",
|
||||
identifier: `resendVerifyEmail.${ctx.user.id}`,
|
||||
identifier: `resendVerifyEmail:${ctx.user.id}`,
|
||||
});
|
||||
|
||||
let emailVerified = Boolean(ctx.user.emailVerified);
|
||||
|
||||
@@ -27,7 +27,7 @@ export const sendVerifyEmailCode = async ({
|
||||
}) => {
|
||||
await checkRateLimitAndThrowError({
|
||||
rateLimitingType: "core",
|
||||
identifier: `emailVerifyByCode.${identifier}`,
|
||||
identifier: `sendVerifyEmailCode:${identifier}`,
|
||||
});
|
||||
|
||||
return await sendEmailVerificationByCode({
|
||||
|
||||
@@ -48,7 +48,7 @@ export const verifyCode = async ({
|
||||
|
||||
await checkRateLimitAndThrowError({
|
||||
rateLimitingType: "core",
|
||||
identifier: hashEmail(email),
|
||||
identifier: `verifyCode:${hashEmail(email)}`,
|
||||
});
|
||||
|
||||
const secret = createHash("md5")
|
||||
|
||||
Reference in New Issue
Block a user