Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion apps/web/test/lib/getSchedule/expects.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { diff } from "jest-diff";
import { expect } from "vitest";

import type { Slot } from "@calcom/trpc/server/routers/viewer/slots/types";
import type { Slot } from "@calcom/atoms/booker/types";

export const expectedSlotsForSchedule = {
IstWorkHours: {
Expand Down
12 changes: 6 additions & 6 deletions biome.json
Original file line number Diff line number Diff line change
Expand Up @@ -337,17 +337,17 @@
"patterns": [
{
"group": [
"@calcom/trpc/react",
"@calcom/trpc/react/**"
"@calcom/trpc",
"@calcom/trpc/**"
],
"message": "atoms package should not import from @calcom/trpc/react."
"message": "atoms package should not import from @calcom/trpc."
},
{
"group": [
"../../trpc/react",
"../../trpc/react/**"
"../../trpc",
"../../trpc/**"
],
"message": "atoms package should not import from trpc/react."
"message": "atoms package should not import from trpc."
}
]
}
Expand Down
32 changes: 31 additions & 1 deletion packages/platform/atoms/booker/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,17 @@ import type {
ApiSuccessResponseWithoutData,
RoutingFormSearchParams,
} from "@calcom/platform-types";
import type { Slot } from "@calcom/trpc/server/routers/viewer/slots/types";

import type { UseCreateBookingInput } from "../hooks/bookings/useCreateBooking";

export type Slot = {
time: string;
userIds?: string[];
attendees?: number;
bookingUid?: string;
users?: string[];
};

// Type that includes only the data values from BookerStore (excluding functions)
export type BookerStoreValues = Omit<
BookerStore,
Expand Down Expand Up @@ -104,3 +111,26 @@ export type BookerPlatformWrapperAtomPropsForTeam = BookerPlatformWrapperAtomPro
routingFormSearchParams?: RoutingFormSearchParams;
rrHostSubsetIds?: number[];
};

type SlotInfo = {
time: string;
attendees?: number;
bookingUid?: string;
away?: boolean;
fromUser?: {
id: string;
displayName: string | null;
};
toUser?: {
id: number;
username: string | null;
displayName: string | null;
};
reason?: string;
emoji?: string;
showNotePublicly?: boolean;
};

export type GetAvailableSlotsResponse = {
slots: Record<string, SlotInfo[]>;
};
50 changes: 50 additions & 0 deletions packages/platform/atoms/hooks/schedules/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
type Schedule = {
id: number;
userId: number;
name: string;
timeZone: string | null;
};

export type CreateScheduleHandlerReturn = {
schedule: Schedule;
};

export type DuplicateScheduleHandlerReturn = {
schedule: Schedule;
};

export type GetAvailabilityListHandlerReturn = {
schedules: (Omit<Schedule, "userId"> & {
availability: {
id: number;
userId: number | null;
eventTypeId: number | null;
days: string[];
startTime: Date;
endTime: Date;
date: Date | null;
scheduleId: number | null;
}[];
isDefault: boolean;
})[];
};

export type CreateScheduleInput = {
name: string;
schedule?: { start: Date; end: Date }[][];
eventTypeId?: number;
};

export function validateCreateScheduleInput(input: unknown): CreateScheduleInput {
if (!input || typeof input !== 'object') {
throw new Error('Invalid input: must be an object');
}

const data = input as Record<string, unknown>;

if (typeof data.name !== 'string' || data.name.length === 0) {
throw new Error('Invalid input: name must be a non-empty string');
}

return data as CreateScheduleInput;
Comment on lines +38 to +49

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Validator is partial; schedule/eventTypeId remain unchecked.

validateCreateScheduleInput only validates name, so malformed schedule or eventTypeId can slip through and fail later. Either validate the full shape (including nested arrays and dates) or rename this to reflect partial validation.

✅ Minimal shape checks
 export function validateCreateScheduleInput(input: unknown): CreateScheduleInput {
   if (!input || typeof input !== 'object') {
     throw new Error('Invalid input: must be an object');
   }

   const data = input as Record<string, unknown>;

   if (typeof data.name !== 'string' || data.name.length === 0) {
     throw new Error('Invalid input: name must be a non-empty string');
   }
+
+  if (data.eventTypeId !== undefined && typeof data.eventTypeId !== 'number') {
+    throw new Error('Invalid input: eventTypeId must be a number');
+  }
+
+  if (data.schedule !== undefined && !Array.isArray(data.schedule)) {
+    throw new Error('Invalid input: schedule must be a 2D array');
+  }
 
   return data as CreateScheduleInput;
 }
🤖 Prompt for AI Agents
In `@packages/platform/atoms/hooks/schedules/types.ts` around lines 38 - 49,
validateCreateScheduleInput currently only checks name; extend it to fully
validate the expected CreateScheduleInput shape (or rename it to indicate
partial validation). Specifically, in validateCreateScheduleInput ensure
eventTypeId exists and is a non-empty string, ensure schedule is an array, and
for each entry in schedule validate required fields (e.g., startTime and endTime
are valid Dates or ISO date strings and any numeric/enum fields have correct
types); throw clear errors for each failure; update any callers/tests if you opt
to rename the function to something like validateCreateScheduleNameOnly to
reflect partial checks.

}
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,9 @@ import { useMutation, useQueryClient } from "@tanstack/react-query";

import { SUCCESS_STATUS } from "@calcom/platform-constants";
import type { ApiResponse, ApiErrorResponse, ApiSuccessResponse } from "@calcom/platform-types";
import type { CreateScheduleHandlerReturn } from "@calcom/trpc/server/routers/viewer/availability/schedule/create.handler";
import { TCreateInputSchema as CreateScheduleSchema } from "@calcom/trpc/server/routers/viewer/availability/schedule/create.schema";

import http from "../../lib/http";
import type { CreateScheduleHandlerReturn, CreateScheduleInput as CreateScheduleSchema } from "./types";
import { QUERY_KEY as SchedulesQueryKey } from "./useAtomGetAllSchedules";
import { QUERY_KEY as ScheduleQueryKey } from "./useAtomSchedule";

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@ import { useMutation, useQueryClient } from "@tanstack/react-query";

import { SUCCESS_STATUS } from "@calcom/platform-constants";
import type { ApiResponse, ApiErrorResponse } from "@calcom/platform-types";
import type { DuplicateScheduleHandlerReturn } from "@calcom/trpc/server/routers/viewer/availability/schedule/duplicate.handler";

import http from "../../lib/http";
import type { DuplicateScheduleHandlerReturn } from "./types";
import { QUERY_KEY as ScheduleQueryKey } from "./useAtomSchedule";

interface useAtomDuplicateScheduleOptions {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@ import { useQuery } from "@tanstack/react-query";

import { SUCCESS_STATUS } from "@calcom/platform-constants";
import type { ApiResponse } from "@calcom/platform-types";
import type { GetAvailabilityListHandlerReturn } from "@calcom/trpc/server/routers/viewer/availability/list.handler";

import http from "../../lib/http";
import type { GetAvailabilityListHandlerReturn } from "./types";
import { useAtomsContext } from "../useAtomsContext";

export const QUERY_KEY = "use-atom-user-schedules";
Expand Down
2 changes: 1 addition & 1 deletion packages/platform/atoms/hooks/useAvailableSlots.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,9 @@ import type {
ApiResponse,
ApiSuccessResponse,
} from "@calcom/platform-types";
import type { GetAvailableSlotsResponse } from "@calcom/trpc/server/routers/viewer/slots/util";

import http from "../lib/http";
import type { GetAvailableSlotsResponse } from "../booker/types";

export const QUERY_KEY = "get-available-slots";

Expand Down
2 changes: 1 addition & 1 deletion packages/platform/atoms/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@
},
"./add-members-switch/AddMembersWithSwitchPlatformWrapper": "./add-members-switch/AddMembersWithSwitchPlatformWrapper.tsx",
"./availability/AvailabilitySettings": "./availability/AvailabilitySettings.tsx",
"./booker": "./booker/index.ts",
"./booker/types": "./booker/types.ts",
"./selected-calendars/SelectedCalendarsSettings": "./selected-calendars/SelectedCalendarsSettings.tsx",
"./components/ui/shell": "./src/components/ui/shell.tsx",
"./dist/index.ts": "./index.ts",
Expand Down
2 changes: 0 additions & 2 deletions packages/platform/atoms/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,6 @@
"../../dayjs",
"../../prisma/zod-utils.ts",
"../../prisma/zod",
"../../trpc/server/routers/viewer/slots",
"../../trpc/server/types",
"../../features/eventtypes",
"../../features/schedules",
"../../features/bookings/Booker",
Expand Down
8 changes: 0 additions & 8 deletions packages/trpc/server/routers/viewer/slots/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -76,14 +76,6 @@ export const reserveSlotSchema = z
"Either slotUtcStartDate, slotUtcEndDate or eventTypeId should be filled in."
);

export type Slot = {
time: string;
userIds?: number[];
attendees?: number;
bookingUid?: string;
users?: string[];
};

export const removeSelectedSlotSchema = z.object({
uid: z.string().nullable(),
});
Expand Down