fix: v2 sentry errors (#21214)
* fix: PATCH, POST event-types Address location must have an address * fix: GET event-types Address location must have an address * fix: POST /v2/bookings Address location must have an address * refactor: GET /v2/slots Invalid time range given. * fix: POST v2/event-types Cannot read properties of null (reading 'type') * fix: POST /v2/bookings/:bookingUid/mark-absent Failed to update no-show status
This commit is contained in:
1 parent
39077a08f1
commit
1a70f83d34
8 files changed
+111
-51
No files matched your search
@@ -20,6 +20,7 @@ import { UsersRepository, UserWithProfile } from "@/modules/users/users.reposito
|
||||
import { ConflictException, Injectable, Logger, NotFoundException } from "@nestjs/common";
|
||||
import { BadRequestException } from "@nestjs/common";
|
||||
import { Request } from "express";
|
||||
import { DateTime } from "luxon";
|
||||
import { z } from "zod";
|
||||
|
||||
import {
|
||||
@@ -594,6 +595,20 @@ export class BookingsService_2024_08_13 {
|
||||
async markAbsent(bookingUid: string, bookingOwnerId: number, body: MarkAbsentBookingInput_2024_08_13) {
|
||||
const bodyTransformed = this.inputService.transformInputMarkAbsentBooking(body);
|
||||
const bookingBefore = await this.bookingsRepository.getByUid(bookingUid);
|
||||
|
||||
if (!bookingBefore) {
|
||||
throw new NotFoundException(`Booking with uid=${bookingUid} not found.`);
|
||||
}
|
||||
|
||||
const nowUtc = DateTime.utc();
|
||||
const bookingStartTimeUtc = DateTime.fromJSDate(bookingBefore.startTime, { zone: "utc" });
|
||||
|
||||
if (nowUtc < bookingStartTimeUtc) {
|
||||
throw new BadRequestException(
|
||||
`Bookings can only be marked as absent after their scheduled start time. Current time in UTC+0: ${nowUtc.toISO()}, Booking start time in UTC+0: ${bookingStartTimeUtc.toISO()}`
|
||||
);
|
||||
}
|
||||
|
||||
const platformClientParams = bookingBefore?.eventTypeId
|
||||
? await this.platformBookingsService.getOAuthClientParams(bookingBefore.eventTypeId)
|
||||
: undefined;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { SlotsOutputService_2024_04_15 } from "@/modules/slots/slots-2024-04-15/services/slots-output.service";
|
||||
import type { RangeSlots, TimeSlots } from "@/modules/slots/slots-2024-04-15/services/slots-output.service";
|
||||
import { SlotsService_2024_04_15 } from "@/modules/slots/slots-2024-04-15/services/slots.service";
|
||||
import { Query, Body, Controller, Get, Delete, Post, Req, Res } from "@nestjs/common";
|
||||
import { Query, Body, Controller, Get, Delete, Post, Req, Res, BadRequestException } from "@nestjs/common";
|
||||
import { ApiExcludeController as DocsExcludeController } from "@nestjs/swagger";
|
||||
import { ApiTags as DocsTags, ApiCreatedResponse, ApiOkResponse, ApiOperation } from "@nestjs/swagger";
|
||||
import { Response as ExpressResponse, Request as ExpressRequest } from "express";
|
||||
@@ -157,6 +157,7 @@ export class SlotsController_2024_04_15 {
|
||||
@Query() query: GetAvailableSlotsInput_2024_04_15,
|
||||
@Req() req: ExpressRequest
|
||||
): Promise<ApiResponse<{ slots: TimeSlots["slots"] | RangeSlots["slots"] }>> {
|
||||
try {
|
||||
const isTeamEvent =
|
||||
query.isTeamEvent === undefined
|
||||
? await this.slotsService.checkIfIsTeamEvent(query.eventTypeId)
|
||||
@@ -171,19 +172,29 @@ export class SlotsController_2024_04_15 {
|
||||
},
|
||||
});
|
||||
|
||||
const { slots } = await this.slotsOutputService.getOutputSlots(
|
||||
availableSlots,
|
||||
query.duration,
|
||||
query.eventTypeId,
|
||||
query.slotFormat,
|
||||
query.timeZone
|
||||
);
|
||||
const { slots } = await this.slotsOutputService.getOutputSlots(
|
||||
availableSlots,
|
||||
query.duration,
|
||||
query.eventTypeId,
|
||||
query.slotFormat,
|
||||
query.timeZone
|
||||
);
|
||||
|
||||
return {
|
||||
data: {
|
||||
slots,
|
||||
},
|
||||
status: SUCCESS_STATUS,
|
||||
};
|
||||
return {
|
||||
data: {
|
||||
slots,
|
||||
},
|
||||
status: SUCCESS_STATUS,
|
||||
};
|
||||
} catch (error) {
|
||||
if (error instanceof Error) {
|
||||
if (error.message.includes("Invalid time range given")) {
|
||||
throw new BadRequestException(
|
||||
"Invalid time range given - check the 'startTime' and 'endTime' query parameters."
|
||||
);
|
||||
}
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -42,24 +42,33 @@ export class SlotsService_2024_09_04 {
|
||||
) {}
|
||||
|
||||
async getAvailableSlots(query: GetSlotsInput_2024_09_04) {
|
||||
const queryTransformed = await this.slotsInputService.transformGetSlotsQuery(query);
|
||||
try {
|
||||
const queryTransformed = await this.slotsInputService.transformGetSlotsQuery(query);
|
||||
const availableSlots: TimeSlots = await getAvailableSlots({
|
||||
input: {
|
||||
...queryTransformed,
|
||||
},
|
||||
ctx: {},
|
||||
});
|
||||
const formatted = await this.slotsOutputService.getAvailableSlots(
|
||||
availableSlots,
|
||||
queryTransformed.eventTypeId,
|
||||
queryTransformed.duration,
|
||||
query.format,
|
||||
queryTransformed.timeZone
|
||||
);
|
||||
|
||||
const availableSlots: TimeSlots = await getAvailableSlots({
|
||||
input: {
|
||||
...queryTransformed,
|
||||
},
|
||||
ctx: {},
|
||||
});
|
||||
|
||||
const formatted = await this.slotsOutputService.getAvailableSlots(
|
||||
availableSlots,
|
||||
queryTransformed.eventTypeId,
|
||||
queryTransformed.duration,
|
||||
query.format,
|
||||
queryTransformed.timeZone
|
||||
);
|
||||
|
||||
return formatted;
|
||||
return formatted;
|
||||
} catch (error) {
|
||||
if (error instanceof Error) {
|
||||
if (error.message.includes("Invalid time range given")) {
|
||||
throw new BadRequestException(
|
||||
"Invalid time range given - check the 'start' and 'end' query parameters."
|
||||
);
|
||||
}
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
async reserveSlot(input: ReserveSlotInput_2024_09_04, authUserId?: number) {
|
||||
|
||||
@@ -9,6 +9,7 @@ import type {
|
||||
OutputLinkLocation_2024_06_14,
|
||||
OutputPhoneLocation_2024_06_14,
|
||||
OutputUnknownLocation_2024_06_14,
|
||||
OutputLocation_2024_06_14,
|
||||
} from "@calcom/platform-types";
|
||||
|
||||
import type { InternalLocation } from "../internal/locations";
|
||||
@@ -50,64 +51,73 @@ export function transformLocationsInternalToApi(internalLocations: InternalLocat
|
||||
return [];
|
||||
}
|
||||
|
||||
return internalLocations.map((location) => {
|
||||
const apiLocations: OutputLocation_2024_06_14[] = [];
|
||||
|
||||
for (const location of internalLocations) {
|
||||
switch (location.type) {
|
||||
case "inPerson": {
|
||||
if (!location.address) {
|
||||
throw new Error("Address location must have an address");
|
||||
continue;
|
||||
}
|
||||
const addressLocation: OutputAddressLocation_2024_06_14 = {
|
||||
type: "address",
|
||||
address: location.address,
|
||||
public: location.displayLocationPublicly,
|
||||
};
|
||||
return addressLocation;
|
||||
apiLocations.push(addressLocation);
|
||||
break;
|
||||
}
|
||||
case "attendeeInPerson": {
|
||||
const attendeeAddressLocation: OutputAttendeeAddressLocation_2024_06_14 = {
|
||||
type: "attendeeAddress",
|
||||
};
|
||||
return attendeeAddressLocation;
|
||||
apiLocations.push(attendeeAddressLocation);
|
||||
break;
|
||||
}
|
||||
case "link": {
|
||||
if (!location.link) {
|
||||
throw new Error("Link location must have a link");
|
||||
continue;
|
||||
}
|
||||
const linkLocation: OutputLinkLocation_2024_06_14 = {
|
||||
type: "link",
|
||||
link: location.link,
|
||||
public: location.displayLocationPublicly,
|
||||
};
|
||||
return linkLocation;
|
||||
apiLocations.push(linkLocation);
|
||||
break;
|
||||
}
|
||||
case "userPhone": {
|
||||
if (!location.hostPhoneNumber) {
|
||||
throw new Error("Phone location must have a phone number");
|
||||
continue;
|
||||
}
|
||||
const phoneLocation: OutputPhoneLocation_2024_06_14 = {
|
||||
type: "phone",
|
||||
phone: location.hostPhoneNumber,
|
||||
public: location.displayLocationPublicly,
|
||||
};
|
||||
return phoneLocation;
|
||||
apiLocations.push(phoneLocation);
|
||||
break;
|
||||
}
|
||||
case "phone": {
|
||||
const attendeePhoneLocation: OutputAttendeePhoneLocation_2024_06_14 = {
|
||||
type: "attendeePhone",
|
||||
};
|
||||
return attendeePhoneLocation;
|
||||
apiLocations.push(attendeePhoneLocation);
|
||||
break;
|
||||
}
|
||||
case "somewhereElse": {
|
||||
const attendeeDefinedLocation: OutputAttendeeDefinedLocation_2024_06_14 = {
|
||||
type: "attendeeDefined",
|
||||
};
|
||||
return attendeeDefinedLocation;
|
||||
apiLocations.push(attendeeDefinedLocation);
|
||||
break;
|
||||
}
|
||||
case "conferencing": {
|
||||
const conferencingLocation: OutputOrganizersDefaultAppLocation_2024_06_14 = {
|
||||
type: "organizersDefaultApp",
|
||||
};
|
||||
return conferencingLocation;
|
||||
apiLocations.push(conferencingLocation);
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
const integrationType = internalToApiIntegrationsMapping[location.type];
|
||||
@@ -116,7 +126,8 @@ export function transformLocationsInternalToApi(internalLocations: InternalLocat
|
||||
type: "unknown",
|
||||
location: JSON.stringify(location),
|
||||
};
|
||||
return unknown;
|
||||
apiLocations.push(unknown);
|
||||
break;
|
||||
}
|
||||
const integration: OutputIntegrationLocation_2024_06_14 = {
|
||||
type: "integration",
|
||||
@@ -124,8 +135,11 @@ export function transformLocationsInternalToApi(internalLocations: InternalLocat
|
||||
link: location.link,
|
||||
credentialId: location.credentialId,
|
||||
};
|
||||
return integration;
|
||||
apiLocations.push(integration);
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return apiLocations;
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
import { ApiProperty as DocsProperty } from "@nestjs/swagger";
|
||||
import { plainToInstance } from "class-transformer";
|
||||
import { IsString, IsIn, IsPhoneNumber } from "class-validator";
|
||||
import { IsString, IsIn, IsPhoneNumber, MinLength } from "class-validator";
|
||||
import type { ValidationOptions, ValidatorConstraintInterface } from "class-validator";
|
||||
import { registerDecorator, validate, ValidatorConstraint } from "class-validator";
|
||||
|
||||
@@ -104,6 +104,7 @@ export class BookingInputAttendeeAddressLocation_2024_08_13 {
|
||||
type!: "attendeeAddress";
|
||||
|
||||
@IsString()
|
||||
@MinLength(1)
|
||||
@DocsProperty({ example: "123 Example St, City, Country" })
|
||||
address!: string;
|
||||
}
|
||||
@@ -126,6 +127,7 @@ export class BookingInputAttendeeDefinedLocation_2024_08_13 {
|
||||
type!: "attendeeDefined";
|
||||
|
||||
@IsString()
|
||||
@MinLength(1)
|
||||
@DocsProperty({ example: "321 Example St, City, Country" })
|
||||
location!: string;
|
||||
}
|
||||
|
||||
+10
-1
@@ -1,7 +1,7 @@
|
||||
import { BadRequestException } from "@nestjs/common";
|
||||
import { ApiProperty as DocsProperty } from "@nestjs/swagger";
|
||||
import { plainToInstance } from "class-transformer";
|
||||
import { IsString, IsUrl, IsIn, IsPhoneNumber, IsBoolean } from "class-validator";
|
||||
import { IsString, IsUrl, IsIn, IsPhoneNumber, IsBoolean, MinLength } from "class-validator";
|
||||
import type { ValidationOptions, ValidatorConstraintInterface } from "class-validator";
|
||||
import { registerDecorator, validate, ValidatorConstraint } from "class-validator";
|
||||
|
||||
@@ -22,6 +22,7 @@ export class InputAddressLocation_2024_06_14 {
|
||||
type!: "address";
|
||||
|
||||
@IsString()
|
||||
@MinLength(1)
|
||||
@DocsProperty({ example: "123 Example St, City, Country" })
|
||||
address!: string;
|
||||
|
||||
@@ -136,6 +137,10 @@ class InputLocationValidator_2024_06_14 implements ValidatorConstraintInterface
|
||||
}
|
||||
|
||||
for (const location of locations) {
|
||||
if (!location || typeof location !== "object") {
|
||||
throw new BadRequestException(`Each object in 'locations' must be an object.`);
|
||||
}
|
||||
|
||||
const { type } = location;
|
||||
if (!type) {
|
||||
throw new BadRequestException(`Each object in 'locations' must have a 'type' property.`);
|
||||
@@ -187,6 +192,10 @@ class InputTeamLocationValidator_2024_06_14 implements ValidatorConstraintInterf
|
||||
}
|
||||
|
||||
for (const location of locations) {
|
||||
if (!location || typeof location !== "object") {
|
||||
throw new BadRequestException(`Each object in 'locations' must be an object.`);
|
||||
}
|
||||
|
||||
const { type } = location;
|
||||
if (!type) {
|
||||
throw new BadRequestException(`Each object in 'locations' must have a 'type' property.`);
|
||||
|
||||
@@ -15,14 +15,14 @@ import {
|
||||
import { SlotFormat } from "@calcom/platform-enums";
|
||||
|
||||
export class GetAvailableSlotsInput_2024_04_15 {
|
||||
@IsDateString()
|
||||
@IsDateString({ strict: true })
|
||||
@ApiProperty({
|
||||
description: "Start date string starting from which to fetch slots in UTC timezone.",
|
||||
example: "2022-06-14T00:00:00.000Z",
|
||||
})
|
||||
startTime!: string;
|
||||
|
||||
@IsDateString()
|
||||
@IsDateString({ strict: true })
|
||||
@ApiProperty({
|
||||
description: "End date string until which to fetch slots in UTC timezone.",
|
||||
example: "2022-06-14T23:59:59.999Z",
|
||||
|
||||
@@ -14,7 +14,7 @@ import {
|
||||
import { SlotFormat } from "@calcom/platform-enums";
|
||||
|
||||
export class GetAvailableSlotsInput_2024_09_04 {
|
||||
@IsDateString()
|
||||
@IsDateString({ strict: true })
|
||||
@ApiProperty({
|
||||
type: String,
|
||||
description: `
|
||||
@@ -29,7 +29,7 @@ export class GetAvailableSlotsInput_2024_09_04 {
|
||||
})
|
||||
start!: string;
|
||||
|
||||
@IsDateString()
|
||||
@IsDateString({ strict: true })
|
||||
@ApiProperty({
|
||||
type: String,
|
||||
description: `
|
||||
|
||||
Reference in new issue
Block a user