* intro work * update wixard form to have content callback to remove preset navigation * more fixes to deployment * fix calling static service * fix save license key text * ensure default steps work as expected * fix conditional for rendering step * skip step * add on next step for free license * refactor wizard form to use nuqs * fix styles * merge base param with step config * fix next stepo text * use deployment Signature token * decrypt signature token * fix: resolve type errors and test failures from wizard form refactor - Fix signatureToken field name to signatureTokenEncrypted in deployment repository - Add missing getSignatureToken method to verifyApiKey test mock - Fix WizardForm import from default to named export in test file - Add missing nextStep prop to Steps component in WizardForm Resolves TypeScript type check errors and unit test failures without changing functionality. Co-Authored-By: sean@cal.com <Sean@brydon.io> * fix: add missing getDeploymentSignatureToken mock in LicenseKeyService test Co-Authored-By: sean@cal.com <Sean@brydon.io> * fix: add nuqs library mock for WizardForm test Co-Authored-By: sean@cal.com <Sean@brydon.io> * fix: add missing nav prop to AdminAppsList component with eslint disable Co-Authored-By: sean@cal.com <Sean@brydon.io> * Apply suggestions from code review Co-authored-by: cubic-dev-ai[bot] <191113872+cubic-dev-ai[bot]@users.noreply.github.com> * Update apps/web/modules/auth/setup-view.tsx Co-authored-by: cubic-dev-ai[bot] <191113872+cubic-dev-ai[bot]@users.noreply.github.com> * fix license schema changes * revret schema generation * fix eslint errors * remove required nav type + add use client * fix types * Update packages/ui/components/form/wizard/useWizardState.ts Co-authored-by: cubic-dev-ai[bot] <191113872+cubic-dev-ai[bot]@users.noreply.github.com> * fix controller issue * add checks for deployment key being null - add more tests * fix tests * add deployment key tests * fix: resolve crypto mock to handle empty encryption keys gracefully - Updated symmetricDecrypt mock to return null instead of throwing 'Invalid key' error when encryption key is empty - All getDeploymentKey tests now pass including the previously failing 'should return null when decryption fails due to missing encryption key' test - Fixes mocking issues in PR 22102 self-hosted onboarding wizard form refactor Co-Authored-By: sean@cal.com <Sean@brydon.io> * fix label * add i18n to error * use enum for steps * add as const * fix test env issues --------- Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Co-authored-by: cubic-dev-ai[bot] <191113872+cubic-dev-ai[bot]@users.noreply.github.com>
103 lines
3.9 KiB
TypeScript
103 lines
3.9 KiB
TypeScript
import { API_VERSIONS_VALUES } from "@/lib/api-versions";
|
|
import { TeamsEventTypesRepository } from "@/modules/teams/event-types/teams-event-types.repository";
|
|
import { Controller, Req, NotFoundException, Param, Post, Body } from "@nestjs/common";
|
|
import { ApiTags as DocsTags, ApiExcludeController as DocsExcludeController } from "@nestjs/swagger";
|
|
import { Request } from "express";
|
|
|
|
import {
|
|
getRoutedUrl,
|
|
getTeamMemberEmailForResponseOrContactUsingUrlQuery,
|
|
} from "@calcom/platform-libraries";
|
|
import { ApiResponse } from "@calcom/platform-types";
|
|
|
|
@Controller({
|
|
path: "/v2/router",
|
|
version: API_VERSIONS_VALUES,
|
|
})
|
|
@DocsTags("Router controller")
|
|
@DocsExcludeController(true)
|
|
export class RouterController {
|
|
constructor(private readonly teamsEventTypesRepository: TeamsEventTypesRepository) {}
|
|
|
|
@Post("/forms/:formId/submit")
|
|
async getRoutingFormResponse(
|
|
@Req() request: Request,
|
|
@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.");
|
|
}
|
|
|
|
if (routedUrlData?.redirect?.destination) {
|
|
return this.handleRedirect(routedUrlData.redirect.destination);
|
|
}
|
|
|
|
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 };
|
|
}
|
|
|
|
return { status: "success", data: { message: "No Route nor custom message found." }, redirect: false };
|
|
}
|
|
|
|
private async handleRedirect(destination: string): Promise<ApiResponse<unknown> & { redirect: boolean }> {
|
|
const routingUrl = new URL(destination);
|
|
const routingSearchParams = routingUrl.searchParams;
|
|
if (
|
|
routingSearchParams.get("cal.action") === "eventTypeRedirectUrl" &&
|
|
routingSearchParams.has("email") &&
|
|
routingSearchParams.has("cal.teamId") &&
|
|
!routingSearchParams.has("cal.skipContactOwner")
|
|
) {
|
|
return this.handleRedirectWithContactOwner(routingUrl, routingSearchParams);
|
|
}
|
|
console.log("handleRedirect Regular called", { destination });
|
|
|
|
return { status: "success", data: destination, redirect: true };
|
|
}
|
|
|
|
private async handleRedirectWithContactOwner(
|
|
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"));
|
|
const eventTypeData = await this.teamsEventTypesRepository.getTeamEventTypeBySlug(
|
|
teamId,
|
|
eventTypeSlug,
|
|
3
|
|
);
|
|
|
|
if (!eventTypeData) {
|
|
throw new NotFoundException("Event type not found.");
|
|
}
|
|
|
|
// get the salesforce record owner email for the email given as a form response.
|
|
const {
|
|
email: teamMemberEmail,
|
|
recordType: crmOwnerRecordType,
|
|
crmAppSlug,
|
|
} = await getTeamMemberEmailForResponseOrContactUsingUrlQuery({
|
|
query: Object.fromEntries(routingSearchParams),
|
|
eventData: eventTypeData,
|
|
});
|
|
|
|
teamMemberEmail && routingUrl.searchParams.set("cal.teamMemberEmail", teamMemberEmail);
|
|
crmOwnerRecordType && routingUrl.searchParams.set("cal.crmOwnerRecordType", crmOwnerRecordType);
|
|
crmAppSlug && routingUrl.searchParams.set("cal.crmAppSlug", crmAppSlug);
|
|
|
|
return { status: "success", data: routingUrl.toString(), redirect: true };
|
|
}
|
|
}
|