Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: v2 slots new version #18758

Open
wants to merge 41 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
41 commits
Select commit Hold shift + click to select a range
55c3862
refactor: version old slots
supalarry Jan 20, 2025
b7d46cb
feat: init new slots endpoints
supalarry Jan 20, 2025
fa12c90
chore: range format
supalarry Jan 20, 2025
49a9f0e
fix: duratin
supalarry Jan 20, 2025
2080f3d
fix: duratin
supalarry Jan 20, 2025
5f54ca4
test: slot releaseAt
supalarry Jan 20, 2025
b827e4b
refactor: reserve slot response
supalarry Jan 20, 2025
f57d53c
refactor variable name
supalarry Jan 20, 2025
463118f
docs: have new slots controller in docs
supalarry Jan 21, 2025
ca180f5
feat: crud for slots reservations
supalarry Jan 21, 2025
4865c63
refactor: use exclude all for response
supalarry Jan 21, 2025
fff2435
docs
supalarry Jan 21, 2025
dfb2f84
chore: slots input service
supalarry Jan 21, 2025
cc361d0
refactor mini
supalarry Jan 21, 2025
8d8fc1f
refactor: remove unused imports
supalarry Jan 21, 2025
24b5c2c
Merge branch 'main' into lauris/cal-5052-platform-refactor-v2-slots
supalarry Jan 21, 2025
7de7d33
Merge branch 'main' into lauris/cal-5052-platform-refactor-v2-slots
supalarry Jan 22, 2025
5c53b24
docs
supalarry Jan 22, 2025
727e8f9
handle orgSlug for dynamic events
supalarry Jan 22, 2025
e70ef3e
refactor: correct name
supalarry Jan 22, 2025
26cae43
docs
supalarry Jan 22, 2025
2f80a35
Merge branch 'main' into lauris/cal-5052-platform-refactor-v2-slots
supalarry Jan 23, 2025
613d442
add optional organizationSlug to BySlug search
supalarry Jan 23, 2025
dde8dd0
Merge branch 'main' into lauris/cal-5052-platform-refactor-v2-slots
supalarry Jan 23, 2025
00a3486
Merge branch 'main' into lauris/cal-5052-platform-refactor-v2-slots
supalarry Jan 23, 2025
fdd1530
refactor: slot output format
supalarry Jan 24, 2025
2df966a
Merge branch 'main' into lauris/cal-5052-platform-refactor-v2-slots
supalarry Jan 24, 2025
f3c2d1d
refactor: return seated slot info
supalarry Jan 24, 2025
ff09e18
docs
supalarry Jan 24, 2025
233ccf1
rename functions
supalarry Jan 24, 2025
6a132bc
refactor: slots seated response
supalarry Jan 24, 2025
96de393
Merge branch 'main' into lauris/cal-5052-platform-refactor-v2-slots
supalarry Jan 24, 2025
599d431
Merge branch 'main' into lauris/cal-5052-platform-refactor-v2-slots
supalarry Jan 24, 2025
25e4bfd
Merge branch 'main' into lauris/cal-5052-platform-refactor-v2-slots
supalarry Jan 27, 2025
a714f2a
Merge branch 'main' into lauris/cal-5052-platform-refactor-v2-slots
supalarry Jan 27, 2025
be7db0c
Merge branch 'main' into lauris/cal-5052-platform-refactor-v2-slots
supalarry Feb 4, 2025
4e6ee60
Merge branch 'main' into lauris/cal-5052-platform-refactor-v2-slots
supalarry Feb 7, 2025
8083059
fix: handle same username in org and non org
supalarry Feb 7, 2025
b4fadd9
refactor: test format
supalarry Feb 7, 2025
0de579b
fix: allow reservationDuration only for authed requests
supalarry Feb 7, 2025
3328ce5
Merge branch 'main' into lauris/cal-5052-platform-refactor-v2-slots
supalarry Feb 10, 2025
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
Prev Previous commit
Next Next commit
feat: crud for slots reservations
  • Loading branch information
supalarry committed Jan 21, 2025
commit ca180f59576529c0c8592cc8cc7d812c8ba10c3c
6 changes: 0 additions & 6 deletions apps/api/v2/src/lib/decorators/cookies.decorator.ts

This file was deleted.

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -1,17 +1,28 @@
import { VERSION_2024_09_04 } from "@/lib/api-versions";
import { Cookies } from "@/lib/decorators/cookies.decorator";
import { GetReservedSlotOutput_2024_09_04 } from "@/modules/slots/slots-2024-09-04/outputs/get-reserved-slot.output";
import { GetSlotsOutput_2024_09_04 } from "@/modules/slots/slots-2024-09-04/outputs/get-slots.output";
import { ReserveSlotOutput_2024_09_04 } from "@/modules/slots/slots-2024-09-04/outputs/reserve-slot.output";
import { SlotsService_2024_09_04 } from "@/modules/slots/slots-2024-09-04/services/slots.service";
import { Query, Body, Controller, Get, Delete, Post, Param, Res, HttpCode, HttpStatus } from "@nestjs/common";
import {
Query,
Body,
Controller,
Get,
Delete,
Post,
Param,
Res,
HttpCode,
HttpStatus,
Patch,
} from "@nestjs/common";
import {
ApiOperation,
ApiTags as DocsTags,
ApiHeader,
ApiResponse as DocsResponse,
ApiQuery,
} from "@nestjs/swagger";
import { Response as ExpressResponse } from "express";

import { SUCCESS_STATUS } from "@calcom/platform-constants";
import {
Expand All @@ -34,40 +45,23 @@ import { ApiResponse } from "@calcom/platform-types";
export class SlotsController_2024_09_04 {
constructor(private readonly slotsService: SlotsService_2024_09_04) {}

@Post("/")
@Get("/")
@ApiOperation({
summary: "Reserve a slot",
description: "Prevent double booking by reserving a slot.",
})
async reserveSlot(
@Body() body: ReserveSlotInput_2024_09_04,
@Cookies("uid") uidCookie: string | undefined,
@Res({ passthrough: true }) response: ExpressResponse
): Promise<ReserveSlotOutput_2024_09_04> {
const reservedSlot = await this.slotsService.reserveSlot(body, uidCookie);

response.cookie("uid", reservedSlot.reservationUid);

return {
status: SUCCESS_STATUS,
data: reservedSlot,
};
}

@Get("/available")
@ApiOperation({
summary: "Get available slots",
summary: "Find out when is an event type ready to be booked.",
description: `
There are 3 ways to get available slots:

1. By event type id: schema ById_2024_09_04. Example '/api/v2/slots/available?eventTypeId=10&start=2050-09-05&end=2050-09-06&timeZone=Europe/Rome'
1. By event type id. Example '/api/v2/slots/available?eventTypeId=10&start=2050-09-05&end=2050-09-06&timeZone=Europe/Rome'

2. By event type slug: schema BySlug_2024_09_04. Example '/api/v2/slots/available?eventTypeSlug=intro&start=2050-09-05&end=2050-09-06'
2. By event type slug + username. Example '/api/v2/slots/available?eventTypeSlug=intro&username=bob&start=2050-09-05&end=2050-09-06'

3. By usernames: schema ByUsernames_2024_09_04. Example '/api/v2/slots/available?usernames=alice,bob&start=2050-09-05&end=2050-09-06&duration=60'
3. By usernames (used for dynamic event type - there is no specific event but you want to know when 2 or more people are available). Example '/api/v2/slots/available?usernames=alice,bob&username=bob&start=2050-09-05&end=2050-09-06&duration=60'

All of them require "start" and "end" query parameters which define the time range for which available slots should be checked,
and "eventTypeId", "eventTypeSlug" or "usernames" query parameters to specify the event type or users for which available slots should be checked.
All of them require "start" and "end" query parameters which define the time range for which available slots should be checked.
Optional parameters are:
- timeZone: Time zone in which the available slots should be returned. Defaults to UTC.
- duration: Only use for event types that allow multiple durations or for dynamic event types. If not passed for multiple duration event types defaults to default duration. For dynamic event types defaults to 30 aka each returned slot is 30 minutes long. So duration=60 means that returned slots will be each 60 minutes long.
- slotFormat: Format of the slots. By default return is an object where each key is date and value is array of slots as string. If you want to get start and end of each slot use "range" as value.
`,
})
@ApiQuery({
Expand Down Expand Up @@ -181,7 +175,51 @@ export class SlotsController_2024_09_04 {
};
}

@Delete("/:uid")
@Post("/reservations")
@ApiOperation({
summary: "Reserve a slot",
description: "Make a slot not available for others to book for a certain period of time.",
})
async reserveSlot(@Body() body: ReserveSlotInput_2024_09_04): Promise<ReserveSlotOutput_2024_09_04> {
const reservedSlot = await this.slotsService.reserveSlot(body);

return {
status: SUCCESS_STATUS,
data: reservedSlot,
};
}

@Get("/reservations/:uid")
@ApiOperation({
summary: "Get reserved slot",
})
async getReservedSlot(@Param("uid") uid: string): Promise<GetReservedSlotOutput_2024_09_04> {
const reservedSlot = await this.slotsService.getReservedSlot(uid);

return {
status: SUCCESS_STATUS,
data: reservedSlot,
};
}

@Patch("/reservations/:uid")
@ApiOperation({
summary: "Updated reserved a slot",
})
@HttpCode(HttpStatus.OK)
async updateReservedSlot(
@Body() body: ReserveSlotInput_2024_09_04,
@Param("uid") uid: string
): Promise<ReserveSlotOutput_2024_09_04> {
const reservedSlot = await this.slotsService.updateReservedSlot(body, uid);

return {
status: SUCCESS_STATUS,
data: reservedSlot,
};
}

@Delete("/reservations/:uid")
@HttpCode(HttpStatus.OK)
@DocsResponse({
status: 200,
Expand All @@ -192,8 +230,8 @@ export class SlotsController_2024_09_04 {
},
},
})
async deleteSelectedSlot(@Param("uid") uid: string): Promise<ApiResponse> {
await this.slotsService.deleteSelectedSlot(uid);
async deleteReservedSlot(@Param("uid") uid: string): Promise<ApiResponse> {
await this.slotsService.deleteReservedSlot(uid);

return {
status: SUCCESS_STATUS,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { ApiProperty } from "@nestjs/swagger";
import { Type } from "class-transformer";
import { IsNotEmptyObject, ValidateNested } from "class-validator";

import {
ApiResponseWithoutData,
GetReservedSlotOutput_2024_09_04 as GetReservedSlotOutputType_2024_09_04,
} from "@calcom/platform-types";

export class GetReservedSlotOutput_2024_09_04 extends ApiResponseWithoutData {
@ApiProperty({
type: GetReservedSlotOutputType_2024_09_04,
})
@IsNotEmptyObject()
@ValidateNested()
@Type(() => GetReservedSlotOutputType_2024_09_04)
data!: GetReservedSlotOutputType_2024_09_04 | null;
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,51 @@ import { Injectable, BadRequestException } from "@nestjs/common";
import { DateTime } from "luxon";

import { SlotFormat } from "@calcom/platform-enums";
import { RangeSlotsOutput_2024_09_04, SlotsOutput_2024_09_04 } from "@calcom/platform-types";
import {
GetReservedSlotOutput_2024_09_04,
RangeSlotsOutput_2024_09_04,
ReserveSlotOutput_2024_09_04,
SlotsOutput_2024_09_04,
} from "@calcom/platform-types";
import { SelectedSlots } from "@calcom/prisma/client";

type GetAvailableSlots = { slots: Record<string, { time: string }[]> };

@Injectable()
export class SlotsOutputService_2024_09_04 {
constructor(private readonly eventTypesRepository: EventTypesRepository_2024_06_14) {}

getOutputSlot(slot: SelectedSlots): GetReservedSlotOutput_2024_09_04 {
return {
eventTypeId: slot.eventTypeId,
slotStart: DateTime.fromJSDate(slot.slotUtcStartDate, { zone: "utc" }).toISO() || "unknown-slot-start",
slotEnd: DateTime.fromJSDate(slot.slotUtcEndDate, { zone: "utc" }).toISO() || "unknown-slot-end",
slotDuration: DateTime.fromJSDate(slot.slotUtcEndDate, { zone: "utc" }).diff(
DateTime.fromJSDate(slot.slotUtcStartDate, { zone: "utc" }),
"minutes"
).minutes,
reservationUid: slot.uid,
reservationUntil:
DateTime.fromJSDate(slot.releaseAt, { zone: "utc" }).toISO() || "unknown-reserved-until",
};
}

getOutputReservedSlot(slot: SelectedSlots, reservationDuration: number): ReserveSlotOutput_2024_09_04 {
return {
eventTypeId: slot.eventTypeId,
slotStart: DateTime.fromJSDate(slot.slotUtcStartDate, { zone: "utc" }).toISO() || "unknown-slot-start",
slotEnd: DateTime.fromJSDate(slot.slotUtcEndDate, { zone: "utc" }).toISO() || "unknown-slot-end",
slotDuration: DateTime.fromJSDate(slot.slotUtcEndDate, { zone: "utc" }).diff(
DateTime.fromJSDate(slot.slotUtcStartDate, { zone: "utc" }),
"minutes"
).minutes,
reservationDuration,
reservationUid: slot.uid,
reservationUntil:
DateTime.fromJSDate(slot.releaseAt, { zone: "utc" }).toISO() || "unknown-reserved-until",
};
}

async getOutputSlots(
availableSlots: GetAvailableSlots,
duration?: number,
Expand Down
Loading
Loading