feat: Add routing-forms record response endpoint with available slots (#22239)

* feat: Add routing-forms record response endpoint with available slots

* fix: resolve TypeScript error in handleResponse.test.ts

- Fix type mismatch where mockResponse was passed as identifierKeyedResponse
- identifierKeyedResponse expects Record<string, string | string[]> structure
- Updated test to pass correct data structure for type compatibility

Co-Authored-By: hariom@cal.com <hariom@cal.com>

* fix: correct POST endpoint parameter handling and request body parsing in routing forms responses controller

- Change @Query() to @Body() decorator for POST request data in controller
- Update service method to accept parsed body data directly
- Remove incorrect URLSearchParams parsing of request.body object
- Fix getRoutingUrl method to use form response data parameter

This resolves API v2 test failures by following proper NestJS patterns for POST request handling.

Co-Authored-By: hariom@cal.com <hariom@cal.com>

* Pass teamMemberEmail as well

* Devin fixes reverted

* Keep all routing related props together in both endpoints

* Remove newly added slots props from Slots documentation as they are used through internal fn call only

* fix test

* Pass skipContactOwner

* Pass crmAppSlug and crmOwnerRecordGType and add more tests

* handle external redirect case and form not found case

* hide props

* chore: bump platform libs

---------

Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Co-authored-by: hariom@cal.com <hariom@cal.com>
Co-authored-by: cal.com <morgan@cal.com>
Co-authored-by: Morgan <33722304+ThyMinimalDev@users.noreply.github.com>
This commit is contained in:
Hariom Balhara
2025-07-04 12:33:13 +00:00
committed by GitHub
co-authored by hariom@cal.com <hariom@cal.com> Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> hariom@cal.com <hariom@cal.com> cal.com Morgan
parent c90fded6b7
commit 056b821070
23 changed files with 1559 additions and 34 deletions
@@ -25,9 +25,9 @@ export class RouterController {
@Param("formId") formId: string,
@Body() body?: Record<string, string>
): Promise<void | (ApiResponse<unknown> & { redirect: boolean })> {
const params = Object.fromEntries(new URLSearchParams(body ?? {}));
const routedUrlData = await getRoutedUrl({ req: request, query: { ...params, form: formId } });
if (routedUrlData?.notFound) {
throw new NotFoundException("Route not found. Please check the provided form parameter.");
}
@@ -37,6 +37,13 @@ export class RouterController {
}
if (routedUrlData?.props) {
if (routedUrlData.props.errorMessage) {
return {
status: "error",
error: { code: "ROUTING_ERROR", message: routedUrlData.props.errorMessage },
redirect: false,
};
}
return { status: "success", data: { message: routedUrlData.props.message ?? "" }, redirect: false };
}
@@ -54,6 +61,7 @@ export class RouterController {
) {
return this.handleRedirectWithContactOwner(routingUrl, routingSearchParams);
}
console.log("handleRedirect Regular called", { destination });
return { status: "success", data: destination, redirect: true };
}
@@ -62,6 +70,7 @@ export class RouterController {
routingUrl: URL,
routingSearchParams: URLSearchParams
): Promise<ApiResponse<unknown> & { redirect: boolean }> {
console.log("handleRedirectWithContactOwner called", { routingUrl, routingSearchParams });
const pathNameParams = routingUrl.pathname.split("/");
const eventTypeSlug = pathNameParams[pathNameParams.length - 1];
const teamId = Number(routingSearchParams.get("cal.teamId"));